Talks

How to write pleasant code

How to write pleasant code

by Roman Kofman

In the talk "How to Write Pleasant Code" presented at RubyConf 2019, Roman Kofman explores how developers can improve their coding practices by focusing on human-centered design principles. He begins by acknowledging the nervousness of being a first-time speaker and sets the tone for a discussion centered around the interaction between human users and the tools they create, especially in coding.

Key points covered include:
- The Nature of Tools: Kofman argues that code should be viewed as a tool not just for functionality, but for usability and joy. He emphasizes that well-designed code leads to a better experience for anyone who interacts with it.
- Refactoring as Key to Usability: He describes refactoring as a critical process for shaping code. By using patterns like DRY (Don't Repeat Yourself) and concepts such as SOLID, Kofman suggests developers can modularize their code to make it easier for others to work on in the future.
- The Balance of Design Principles: For every principle like DRY, there are counter-principles such as YAGNI (You Aren't Gonna Need It), illustrating that coding approaches can vary widely depending on the scenario and requirements.
- Human-Centered Design: The talk delves into human-centered design as a framework that emphasizes understanding the user—seeking to create solutions that are intuitive, considering the user's context, knowledge level, and environment. Kofman highlights the importance of observing users and exploring edge cases to ensure that the code remains accessible and helpful to all.
- Feedback Mechanisms: The importance of providing adequate feedback for user actions is stressed, allowing users to learn and adapt as they work with the code. This feedback loop enhances user experience and confidence.
- Deciding Code Structure: Kofman provides a personal anecdote about managing code changes, emphasizing that the decision on whether to combine elements of code—or separate them—should be influenced by their interactivity and the potential for unintended consequences.

In conclusion, Kofman advocates for a perspective of writing code that prioritizes the happiness and effectiveness of users, suggesting that developers step back from focusing solely on code purity. Instead, they should consider whom the code is for and how it will impact them, leading toward creative solutions that foster collaboration and usability in coding environments.

Overall, the takeaway is clear: tools, including code, should enhance user capability and experience, echoing a larger philosophy of design being deeply rooted in understanding human needs and behaviors.

00:00:12.830 Hi there, my name is Roman, and I work at Square building APIs. I'm here to talk to you about writing better code. Now, before we get started, I want to give a brief warning: this is my first time professionally speaking at a conference, so I'm a little bit nervous.
00:00:25.740 If I get stuck, please give me a round of applause. I'll take a sip of water, and then we can keep going.
00:00:31.859 The reason there’s a hammer up there when talking about writing good code is that I'm keenly interested in how the human mind relates to the tools we use. Scientists have studied a quirk of the brain that effectively incorporates the tools we use into a representation of our own body.
00:00:43.679 When we swing a hammer, the brain treats our hand and the hammer similarly. It makes no clear distinction between the two. When we drive a car, we just think, 'Let’s turn left.' We don’t think about moving our hands this way or using our feet like this to move the car.
00:00:55.139 That combination is absolutely magical. A good tool is exhilarating to use. We become augmented, able to do things we weren't able to do before, and every time the tool does exactly what we want it to do, we get a jolt of joy — a feedback loop that tells us, 'Yes, I can do this. I am powerful.'
00:01:07.170 When a tool doesn’t work as expected, it’s one of the most jarring experiences possible; it feels almost as if our own body is betraying us. It’s entirely unpleasant.
00:01:14.670 Now, I would argue that code, when rewritten, is itself a tool for producing future software. Either ourselves or other developers will use it to build new features and ship new code in the future. For many of us, even if we're building a product, that code will live on and facilitate a lot more products than what we're currently working on.
00:01:26.940 That means, broadly speaking, we are designing a tool, even if we’re not aware of it in the moment. Every time you write code, it’s really important for the tools to be well-designed.
00:01:41.260 Matz agrees with me, and it's part of why he created Ruby the way he did. He wants people to experience joy when programming, when using the tool of Ruby. I think the same applies to us: when we write code, we must consider not just whether it works or is efficient, but also how it will function for the next person who makes a change to it.
00:01:50.999 The aspect of change is really important to me. It’s why I believe usability is a better word than readability when talking about code. Code is not static; it’s something that people will work with to do things, so we need to ask: will it make people happy, or will it make them sad?
00:02:06.000 Some of that is quite selfish: I want the code I leave behind to be code that I'm happy to work on. I want others to feel the same about the code they inherit from me, but some of it is altruistic; being kind to my coworkers really matters to me. It’s a core motivation in my day-to-day work.
00:02:20.290 So, what can we do to make code better? What tools do we have for shaping code? One of my big revelations when I entered the professional software world after graduating college was the concept of refactoring code.
00:02:38.680 This idea that you can move code around and change its shape without changing its functionality is crucial. Martin Fowler wrote the canonical book that catalogs a variety of refactoring patterns and what you can do to code to change its shape.
00:02:52.560 For example, there’s a hide delegate pattern where if your object depends on an object that depends on another object, you can remove the dependency by adding a little method. You don’t need to read the details, don’t strain your eyes!
00:03:07.030 What I find really interesting about refactoring is that for almost every single refactoring, there is an equal and opposite reaction that moves code in the other direction. So, whether to move code forward or backward is a judgment call.
00:03:19.989 There are code smells to look for, but different people smell different things as bad. So, the answer that senior programmers often tell you is: 'It depends.' I see you, senior developers!
00:03:36.370 But that’s kind of awkward, and we really want to have decision frameworks. Are there frameworks that help us choose what direction to push the code in? The answer is actually yes; there are concepts like DRY (Don't Repeat Yourself).
00:03:52.560 That's the first concept I ever learned from this group. If code looks similar, even if you kind of step away and squint, and the shape looks similar, there’s probably something there to refactor.
00:04:05.700 There’s probably a concept that you can name, and you can refactor it to make the code easier to work with. There are also a few other concepts; SOLID is really a collection of principles.
00:04:21.080 The takeaway from all these principles is that they are different ways to approach the same general idea: modularize everything. Split your code into tiny modules that work well together.
00:04:35.510 In our strive for perfection, developers tend to over-optimize. We work so hard to write clean code that we lose the forest for the trees and create overly complex structures in the process.
00:04:52.850 And so, perhaps surprisingly to nobody, there is an equal and opposite reaction to every code design principle. For DRY, there’s YAGNI (You Aren't Gonna Need It), which is a principle popularized by Sandi Metz within the Ruby community.
00:05:01.560 YAGNI warns that the wrong abstraction is much worse than duplication. There’s also the idea of composition over inheritance, which in my mind is a rebellion against SOLID, primarily how to do inheritance right in Java.
00:05:12.580 These counter-reactions aren’t just theoretical; they were invented by frustrated engineers dealing with real production issues.
00:05:24.000 Let me illustrate this with a specific issue I encountered with code. Imagine we have a very simple HR application that needs to represent employees. It will have a page to show a single employee and a page to list all employees.
00:05:36.150 The code to find an employee and send it to the front end looks something like this. There is a serialized method that takes the employee's data and converts it into JSON format or something similar. This is pretty straightforward; it works great.
00:05:50.520 Then, a feature request comes in: 'Add a job title to the employee page.' I put on my developer hat, roll up my sleeves, and try to make this change. I look at the show page and see how the employee is being presented.
00:06:12.000 This isn’t actually showing me where the fields are, so I click into the employee presenter and find the serialize method. That’s where the fields are, and I think: 'This is simple. I’ll add a field for the job title.' So I find the job title and print it out. Done and dusted!
00:06:28.810 But remember, that presenter is actually used in multiple places. Let’s zoom in on the other use case: we loop through every employee and render it over and over again, which means if looking up an employee's job involves a database call, we create an N+1 query.
00:06:45.750 This means that for every single employee, we forget that we've queried the database and ask it again. This is a problem; it will crash your server, slow down your page, and might even wake you up at 5:00 a.m., not that I know that from experience!
00:07:01.400 So, let’s talk about what failed here. One could say we should have never added the work of looking up the job title into the presenter; it’s too much responsibility for that class.
00:07:13.000 Maybe it should have been passed in. If I were working only with people who were exceptionally solid at separating responsibilities and dealt with the code exactly as I would have, then we wouldn’t have a problem.
00:07:30.180 But users will surprise you! I would never think of hanging a hammer like this; it freaks me out honestly. What if it falls? Will it hurt someone? If you see something like this happen, if one person does something you didn’t expect, I guarantee that other people will as well.
00:07:46.410 So we have to start designing for it. Perhaps the original sin is actually combining two things that don’t belong together because they don’t change together. Maybe there needed to be two presenters all along, and we could just name the other one 'EmployeeListPresenter.'
00:08:01.230 This way, a change in one presenter won’t affect the other, and everybody's happy. If you're groaning at this because it sounds like the same code written twice in two different classes, I’m with you; this pains me to see.
00:08:16.181 But at least it won't wake you up at 5:00 a.m.! I have seen teams argue for hours over questions that essentially boil down to: 'Should we combine this code or not? Will it change together?'
00:08:27.090 How should we shape the code to best represent the concept at hand? I think we actually over-focus on code structure, and that’s part of the problem. The original sin is this idea that we are trying to achieve clean code in the first place.
00:08:43.140 When looking at other tools, we don’t refer to them as cleanly designed; we don’t say, 'Oh, that rake has too many prongs; it’s not cleanly designed enough.' No, we focus on how well it works — at gathering leaves.
00:08:58.960 We focus on the work we want to do with it and whether it performs well. The whole idea of calling something a limitation of a tool or shaming people who don’t adhere to our particular standards of purity is toxic.
00:09:07.090 If you have slightly different views on what that purity should look like, that's an easy way to bring a team to a standstill. So perhaps we are asking the wrong questions.
00:09:23.420 When we look at two pieces of code that perform identical operations, we often debate which one is better in an absolute moralistic sense. To illustrate, let’s take the analogy a little bit further. Let’s compare tools.
00:09:36.980 The hammer on the left is used for working on gold jewelry; the one on the right is a sledgehammer. Which one of them is better as a tool? On its face, that question is completely nonsensical.
00:09:55.430 The question is, what are you trying to use it for and who is going to be using it? Each tool achieves a different result, used by different people with different expectations, in different contexts.
00:10:09.990 So to figure out which to build, we need to remember the most important aspect of tools: they are made for people to use. In my opinion, as I was doing research on Flickr about hammers, this is by far the best hammer.
00:10:24.740 It’s structurally unsound; it won’t drive a nail, but it’s such a joy to play with. Matt's agrees with me on this — at least the need to center people.
00:10:38.030 Now, I want to talk about design in a general sense that transcends code. Fundamentally, design is about creating solutions to complicated problems, usually a mix of constraints and open-ended questions.
00:10:51.150 The field of design is essentially a realm of applied creativity, and its frameworks help designers build up context about a subject so that they can take intuitive leaps to what are sometimes completely unexpected solutions.
00:11:05.020 In some ways, the point of these frameworks is to help your brain reach a point where those intuitive leaps are easier to make. It’s about improving the odds of those creative leaps.
00:11:21.350 More specifically, since we're focusing on humans, I want to talk about human-centered design; it's a perspective on design that has gained popularity over the past thirty years. It centers on people as the narrative that drives decisions throughout the design process.
00:11:41.440 Human-centered design was coined by Don Norman’s research lab. Don Norman is a cognitive scientist turned engineer and human factors researcher. He has worked as a safety investigator for aviation and other industries, including being one of the investigators selected to investigate the Three Mile Island incident.
00:11:56.670 This guy really knows about how humans interact with tools, and he wrote a book regarded as the canonical text in usability design. If you take nothing else away from this talk and haven’t read this book yet, please lookup and read it. It’s amazing.
00:12:09.730 By the way, notice the teapot on the cover; it’s a piece of art called the masochist teapot because it’s impossible to use.
00:12:22.920 Human-centered design, sometimes referred to as user-centered design (I prefer 'human' because it's broader), focuses on three points of view for analysis: the person, the context, and the action that they’re going to take.
00:12:36.320 Let’s discuss each of these individually. When considering a person using a tool, like a hammer, you will want to know how strong they are, how large their hand is, and what grip they need. Are they trained in using a hammer, or do you need to design it so that they can’t hurt themselves even if they use it incorrectly?
00:12:50.960 For coders, we also need to keep this in mind. Is the coder a senior tech lead with decades of experience who can debug complex issues in under two hours? Or is this person perhaps at their first tech job, needing a lot of hand-holding, and doesn’t even know about refactoring yet?
00:13:05.650 Taking this into account is really important. The key here is to be specific and make the users real in your mind. It’s not about micro-targeting characteristics like for a Facebook ad; it’s about understanding their intuition and how they might think or act.
00:13:22.950 If you don’t have that intuition, go out and observe how those types of users actually interact with the tools you're considering. Observing a junior engineer using code for the first time is really different from observing a senior engineer.
00:13:37.400 And if you haven’t seen it in a while, ask a teammate to show you how they work. You can silently observe, maybe ask them why they are doing something, or conduct a usability study within your team.
00:13:51.200 Another key is to explore edge cases. There’s a reason I mentioned the contrast between a very experienced person and one who is very junior; their needs and expectations, as well as the mistakes they make, will differ significantly.
00:14:06.930 Next up is context. Hammers designed for different settings will have distinct requirements because the users will have different points of view. For example, if you use a hammer in an operating room, it must be easy to sterilize; otherwise, it's completely useless after just one use.
00:14:22.560 On a farm, this isn’t a concern at all. In the context of code, similar questions arise.
00:14:36.280 Let me share a story. I was working on a side project with a friend, and I couldn't believe the code he was writing — it was gnarly, with if statements in for loops and maps, making it difficult to read. I come from a background of writing very cleanly and naming everything carefully.
00:14:50.840 We clashed until I realized he came from the movie animation industry, and we each had about five years of experience at that point. In movie animation, as long as you render an animation once and it looks good, there's no need to go back and refactor it for other projects.
00:15:06.470 However, in the world of product development, products last for years and are developed over time, which creates a completely different context. Neither of us was absolutely right, so what we needed to do was communicate and figure out what approach made sense for that specific project.
00:15:22.860 Adjusting to others' contexts is really important. When thinking about the context in which people work, consider the knowledge they will bring to the problem, how they will acquire it, and what their intentions will be.
00:15:35.380 They won't have all the knowledge you possess. Their intentions may be specific, often involving a particular change — for instance, with a web form, users are likely to add or remove fields.
00:15:49.780 You won’t have to be perfect at predicting the future; take educated guesses and explore edge cases repeatedly to build your intuition.
00:16:05.410 Finally, let's discuss the social environment. Will the team be well-supported? Will they engage in pair programming? Are people from another team going to ship code to your team? Do they feel they have enough ownership to change the structure, or do you need to guarantee that the fundamentals are correct because others will interact with it?
00:16:19.260 These are all crucial considerations to create a tool that is genuinely useful for those who will be working on it. The last concept we need to touch on is action. This is the most complex aspect, and I won't cover it deeply here.
00:16:35.230 You could read entire books on how we perceive things and translate intentions into action and products in the world. However, I will share this diagram from Don Norman’s book about where mistakes occur, how inaction translates into intention, performance, and back again.
00:16:50.470 In this diagram, it’s important to note how we can provide information ahead of time before a person chooses to act, or feedback afterward, to guide them toward better paths.
00:17:02.690 When people approach a tool, they typically familiar themselves with it first. If it's a hammer, they might lightly tap the nail before starting to hammer harder. The same goes for code; a person won’t ship a completely new architecture right away.
00:17:16.160 They will first attempt small changes, observe how the system reacts, and build their confidence from there. If you do your job well and write code that guides behaviors with feedback and error messages, they will gain confidence much more quickly.
00:17:30.880 The keys to consider when thinking about action are perception, then action, followed by feedback. You perceive a situation before you act, then you act, receive feedback, reflect on it, and repeat the cycle.
00:17:44.570 At every step in this cycle, there can be mistakes and opportunities to improve it. Touching on human-centered design again, it serves as a framework for making decisions and communicating those decisions from the perspective of a person, their context, and the action they intend to take.
00:17:56.440 I believe you can use this approach to resolve disputes. Step back from focusing solely on structure, and ask who it affects. Does the structure make it less risky for them to implement a change? Does it help them in some way?
00:18:08.490 If the answer is that neither structure benefits the user, then it may not matter which one you choose. In such cases, flip a coin, because if you can’t connect it back to how it affects people, then maybe the decision isn’t really that important.
00:18:21.310 Returning to the earlier issue of whether we should have one presenter or two: our team decided to have one presenter but included a caveat. We examined where it would be used and realized there would likely be unintentional changes causing side effects elsewhere.
00:18:37.780 To mitigate this, we modified the code to improve affordances and feedback. We added a feature we call 'without DB,' which wraps a block of code and replaces the database connection with a simple class that throws an exception if it’s used.
00:18:52.780 This way, we focused on failure cases—how the system can guide users toward better behavior without expecting everyone to code perfectly all the time.
00:19:04.030 We realized we could change the code in a way that provided feedback; now, if someone isn't looking at this class and attempts something dangerous, they will receive a warning.
00:19:19.510 If they are performing a safe operation, the tool should stay out of their way. Tools that constantly cause users to trip over them, even when everything is done correctly, are not good tools.
00:19:43.410 Thank you! So, that’s what I want you to apply to your code, especially when you’re uncertain of the direction to take it.
00:19:54.780 Sometimes the answer is clear; structure will guide you, as every hammer has properties it must adhere to. But when you're unsure—particularly during discussions with your teammates—take that step back.
00:20:09.160 Use this framework to ask yourself: Who is it for, and how will it impact them? This can lead you toward creative solutions that you may not have thought of initially.
00:20:22.450 Lastly, I want to share some inspirations from this talk. Here’s a list of videos and books that I highly recommend if you’re interested in these subjects.
00:20:35.700 These works are not at all comprehensive but represent the learning that has shaped my thought process. I want to especially call out 'Web Attacks,' which was my first job and where I learned about design and contextual design research.
00:20:52.620 They are a Chicago-based design research and development firm. Thank you for being here, and thank you for supporting me up here.
00:21:07.880 And thanks to Square for allowing me to come out here and speak. Are there any questions?
00:21:18.230 One question asked whether the 'without DB' function is open source. Not yet, but it was based on a small tweak to an open-source gem. If you are interested, come speak to me after we wrap up.
00:21:32.140 I have stickers of lemurs from Brandon Stock and a lot of horror stories about awful UX and code, as well as tales regarding the senior architect who inspired me to talk about this subject. They are not part of Square, so don’t worry!
00:21:47.680 I have many stories to share if you're interested. Thank you!