• Hey Amro, what’s on your desk?

    A mostly wire free desk aimed from the right side showing various work equipment: a large monitor, a small laptop to the right and one behind in a stand, two key lights hovering over the desk, a microphone, webcam, keyboard, mouse, trackpad, volume knob, and a notebook, pen, and more
    Amro’s desk

    I shared a Tweet with a photo of my current work setup and a few folks asked what I use so I thought I’d put together a blog post with all the hardware on my desk.

    I have conflicting goals in choosing the gear I use to do my work. For instance, I want to minimize visible wiring and also want a nice, tactile keyboard. There are decent wireless mechanical keyboards out there, but the ones I’ve tried don’t suit my needs (I have tried many).

    I do a lot of video conferencing for work so want my A/V setup to be of high quality. To that end, I use a mirrorless camera, decent lighting, and a good microphone. I keep personal stuff off my work laptop, so my personal laptop needs to be available to me for personal communication.

    Anyway, here’s a list of my gear:

    Desk: Fully Jarvis Standing Desk – A pretty nice standing desk. It’s fairly stable, though a little wobbly. I’d avoid the black desk top if I bought again as it shows dust easily. Wire management underneath is kind of a pain as the metal frame does not have through holes. I like it a lot otherwise.

    Desk mat: Grovemade Wool Felt Desk Pad (Medium/Light) – I like this mat quite a lot: it’s not scratchy, and is a decent mouse pad. My one complaint is it slides around a bit too easily, though my heavy keyboard helps keep it in place. It doesn’t have rubber or silicone on the bottom for friction.

    Monitor: Apple XDR Pro Display and stand – overpriced, but sharp image, and aesthetically pleasing. Nice for photo editing, which I do.

    Keyboard: GMMK Pro with Kailh Box White switches and these keycaps. Some assembly required, but really nice to type on. Clicky, but not so much actuation force that your fingers feel dead at the end of the day. I like a ten-keyless keyboard for its compact size, and I also like having a function row. The volume knob is a nice addition. I did need to modify the firmware slightly to get key mappings right for the Mac. That was kind of a pain, and also fun.

    Mouse: Logitech MX Master 3 – This mouse is comfortable to use for long periods of time and the battery life is great. one downside is the buttons tend to get shiny over time (ABS plastic?). The Mac version lacks Logitech’s Unifying adapter, and Bluetooth support is jumpy in my experience, so you should get the non-Mac version. It works fine with a Mac.

    Trackpad: Apple Magic Trackpad 2 in space gray – super nice, and great for switching between desktop spaces. Excellent battery life, but switching between machines is a little annoying. I put mine to the left of my keyboard.

    Cameras: Fuji X-T30 + 23mm f/2 lens and Logi 4K Pro Magnetic Webcam for Pro Display XDR – I had the Fuji camera laying around after getting a new mirrorless camera, and I bought the lens second hand off a friend to save money. Note this Fuji camera has been replaced by a newer model. If I were buying from scratch I’d probably look at a less expensive mirrorless camera. The Logitech webcam is decent, but the video quality is just so-so, and there’s no bokeh. It’s a nice backup in case something goes wrong, and I appreciate that it attaches to my monitor magnetically and looks nice.

    Camera arm: Elgato Master Mount – A stable camera arm with a nice desk clamp. The side of the clamp is low profile, and the clamp mechanism handle features a ratchet feature that makes it easy to secure in tight places.

    Video capture: AVerMedia Live Streamer Cap 4K – An excellent USB video capture device. While Fuji makes software that lets one use their cameras as webcams via USB, it does not yet work with M1-based Macs, the output is limited to 720p, and it only works in some software (e.g. works in Chrome, not in Slack or Safari). Initially I tried an Elgato Cam Link 4K, which mostly worked, but has issues playing nicely with other high-bandwidth USB devices on the same bus. For instance, video output from the Cam Link 4K freezes after a few seconds when using a Thunderbolt hub connected to an M1-based Mac.

    Lighting: Elgato Key Light Air x 2 – Proper lighting is important for optimal viewing, and key lights are a good place to start. A three point setup would be great, so do some research/planning.

    Headphones:Apple AirPods Max – Expensive, but they sound nice and switch between devices quickly and easily. The battery lasts quite a long time — I burn through about 30% in a typical workday. Amazon sometimes has discounts on these headphones, so I’d check there first.

    Headphone hook: Audio-Technica AT-HPH300 – Soft, inexpensive, and won’t ruin your headphones

    Microphone and interface: Shure SM7B, Elgato Wave XLR, Cloud Microphones Cloudlifter CL-1 – The SM7B is an iconic microphone that delivers excellent sound, but it’s expensive. Check out the Shure MV7 to get a similar sound at a much lower price. You have to put up with branding on the side though. I like the Wave XLR because the cables plug into the back, it has a large volume knob, and a capacitative mute button on top. The SM7B is a notoriously quiet microphone, and the Cloudlifter amplifies it by 20+ db without increasing noise substantially. You won’t need a Cloudlifter if you go with the MV7, and if you use it via USB you won’t need an XLR interface either. Another popular interface is the Focusrite Scarlett 2i2. I went with the Elgato over this as cables coming out the back of the unit makes cable management easier.

    Boom arm: Rode PSA1 – A classic boom arm. This may seem basic, but the boom arm stays where you put it. Others I’ve used tend to sag, etc. The desk clamp is just okay for me. If I were buying today I’d get the Rode PSA1+ instead, which fixes some minor annoyances with the PSA1. Following the trend, the PSA1+ does have prominent branding on it, which I find mildly off-putting. I’d still buy it though.

    Dock: OWC Thunderbolt 4 Dock – A true one-cable solution! All my peripherals plugs into this dock. One cable goes from this dock to my laptop and keeps my laptop charged. This is one of the cheaper Thunderbolt 4 docks on the market, but they all seem to follow the same reference design and OWC makes good hardware in my experience.

    Ethernet: Sonnet Technologies Solo 10G – A Thunderbolt 10gbps ethernet adapter. It’s nice, but no longer made. I’d recommend OWC’s current offering instead.

    Charing cable: Anker Powerline II 3-in-1 Cable – I use a U2F security key and occasionally need to charge my mouse and trackpad. This cable has the 3 most common plugs I need to charge my peripherals, and I can keep my U2F key attached to the USB-C plug for easy access.

    Laptops: Work provided 15″ 2018 Macbook Pro, and a 2020 14″ Macbook Pro for personal use – I won’t bother linking to these. The work machine is getting a bit old/slow, even for my use. The newer personal machine is a dream to use for programming, photo editing, and shitposting on Twitter.

    Laptop stand: OMOTON Vertical Laptop Stand – I use this relatively inexpensive, adjustable laptop stand. My Thunderbolt dock sits behind my laptop in the second groove. The logo is only present on one side so turn it around for a cleaner look.

    Did I leave something out? Lemme know on Twitter and I’ll add it to this list.

  • Approaching Code Review

    Code review is great way to maintain and improve your team’s codebase, and like a lot of team process having an intentional, positive culture around it makes it better. One of my earliest patches at Twitter was adding a short-lived token-based system for logged out requests (think login, signup, etc.). The patch itself wasn’t so large — a few hundred lines of Objective-C plus tests and integration, but it was important enough to require a fair bit of scrutiny in code review. This change was risky, and it wasn’t something we could easily test incrementally in production so we needed to get it right.

    Like all our changes, my patch went through code review and it ultimately took many revisions for me to get it right. Code review can feel intimidating, but in this case I felt supported as my reviewer displayed empathy and patience while helping me find mistakes. That review helped set the tone for how I’d work with others in the future so I thought I’d share a bit of what I’ve learned about approaching code review on large teams.

    My job as a reviewer

    My job as a reviewer is to help make my coworker’s contributions better by helping them catch mistakes as well as helping them improve their technical solution. My job is not to gate keep to get my way. One natural consequence of this is it enables authors to express creativity in their work, which leads to more engaged teammates. Of course, it’s really important to teach others when there’s a better way to solve problems and doing so gracefully is part of providing effective code review. That’s a core part of code review, but it’s not what this post is about.

    Sometimes there are established team norms one needs to follow for the sake of consistency in a codebase — e.g. stylistic things. We’re not here to talk about tabs vs. spaces, sorting imports, or whether braces belong on a new line either though (real talk: spaces, yes, and absolutely not 😇). I’ve found it’s helpful to move these sorts of things to automated linters, where possible, as it removes the need to raise them in code review altogether. This maintains consistency, simplifies and speeds up code review, and removes the potential for animosity over minor stylistic change requests.

    Speaking of norms, your team might consider setting one for the upper bound on the size of a typical patch. Exceptions will be made and that’s okay, but keeping patches small means reviewers don’t need to spend as much time on each code review pass. This is not only considerate of teammates’ time — it also helps make space for their other work. Sticking to reasonably sized patches also makes it easier for multiple people to work on the same project as it reduces the times which one person might be blocked waiting on another. I’ve seen larger patches work out fine on other teams — it depends on your team’s needs.

    Framing feedback

    Framing code review feedback in a way that’s clear and non-confrontational helps avoid the misunderstandings that sometimes result from written communication. Written communication can be devoid of the tone that smooths over mistakes in verbal communication. I ask myself how I might receive a message to help refine the wording so it’s more likely to be understood as I intend. Importantly, the wording I choose critiques the work I’m reviewing and not the person.

    Working relationships can also affect how you communicate in code review. Getting to know your teammates helps you understand their communication style, which helps you give each other the benefit of a doubt when reading, for instance, a terse reply. However, you won’t always have the luxury of knowing how others work and communicate. Whether you’re working with a new teammate or your team is so large that it’s impractical to know everyone, it’s important to consider how framing your feedback might affect how it’s received.

    Another way to approach communication, in code review and beyond, is leading with sincere curiosity. Curiosity helps disarm negative emotion when another person receives your commentary. For instance, something like “did you know we have a utility function for this?” along with a function name is better than sharing only the function name or a link with no additional context because it doesn’t unintentionally imply the author has done something wrong, is lacking expertise, etc.

    Context matters

    When reviewing code I try to keep in mind the author may have context I don’t and vice versa. They may have spent more time on the patch than the number of lines changed indicates, and they might have constraints I’m not aware of like deadlines or other urgent work to get to. I ask questions to gather the context I need to provide helpful code review up front. Do I clearly understand what the patch is supposed to do? Should it have automated tests? Does it have manual testing instructions so I can verify it works? A proper commit message? Documentation where applicable?

    I then leave comments where I think something can be improved. I like to prepend “optional: ” or “nit:” where I have minor suggestions that shouldn’t block merging the patch so the author knows they are non-blocking. The author makes the call on whether to address those comments. The patch is their contribution, after all.

    If a patch requires substantial rework — maybe I feel I’m leaving too many comments, or sense we’re talking past each other in code review, then I take the discussion to a richer communication medium. For instance, if we’re having a discussion on a code review tool, then I’ll send a private message on Slack to ask if we can chat there. If we’re talking on Slack then I’ll ask if we can have an audio or video call. There’s a bit more to this though: Slack is richer than a typical code review tool because it moves the conversation from a public space to a private one, and it enables live communication. The former can help dial down emotions, but it doesn’t necessarily help with tone as that requires intentional word choice, especially in written communication. It’s worth keeping in mind the culture one grew up with may also affect how they interpret your words.

    Earlier I mentioned tone helps bridge understanding. Moving to a richer communication medium is one way to add tone to the discussion. I’d be remiss if I didn’t mention it’s important to note any potential accessibility considerations and working hours for colleagues when switching communication mediums.

    Avoiding wording that make assumptions about the other person’s knowledge is part of understanding teammates may have different context. I try to avoid words like “obviously” or “just” because what’s obvious or simple to me may not be so obvious or simple for someone else. Perhaps they’re new to some part of the codebase, the team, or are earlier in their career.

    Wrapping up

    I’ve certainly done all these things wrong in the past, and I’ve been on the receiving end of code reviews from colleagues who have done the same. It wasn’t always clear to me when I made a mistake as a reviewer until later, but it was usually clear to me when I felt let down by the way someone delivered feedback on my work. Being cognizant of your job as a reviewer, framing, and context you have (and don’t) can help you avoid unnecessary strife with your teammates.

    There are also things you can do as an author to make code review easier on yourself and your colleagues, as well as ways you can defend your time and approach where needed. We’ll talk about those things in a future post.

    Follow me on Twitter at @amdev if you like puns or pizza.

  • Facilitating Instead of Dictating

    Early in my career I thought showing my worth on a team meant having my ideas win. I was far too attached to those ideas, which left me frustrated when they weren’t chosen and worst of all made working with others more difficult. Over time I learned the right approach wasn’t to dictate solutions, but to facilitate problem solving. This approach has benefited everyone involved, led to better solutions, and to better relationships.

    Being a Dictator

    Dictating solutions, be it as an engineering lead or a product manager or from entirely outside a team, creates a single point of failure and stifles the creativity of one’s team. The dictator becomes a bottleneck for the team, which slows down decision making, robs others of a chance to grow and contribute, and ironically stunts the dictator’s own growth as they spend time answering questions for others rather than looking ahead. Dictating solutions also alienates others and harms working relationships when mixed with poor communication or aggression.

    Often this sort of behavior isn’t malicious, the dictator may even believe they’re helping. People inadvertently cause harm in an attempt to be the go-to person, doing their best to remove a bit of the burden from others. That’s what I thought I was doing many years ago, and it wasn’t until I saw others facilitate that I realized the benefits of intentionally making space for others to contribute.

    Being a Facilitator

    Leading as a facilitator means coming to one’s team with a problem instead of dictating a solution, then working with them to find the right approach regardless of whose idea it is. This shows the team their input is valued, which helps them become invested in the work and it makes space for others to learn, teach, and show their creativity.

    Sometimes there’s a lack of ideas because the problem space is poorly defined or too big. In the former case I like to ask questions to help better frame the problem, and in the latter I like to break the problem down into smaller problems to be solved. If there are many problems I like to explicitly delegate finding a solution to each problem to a single person. They don’t have to figure it out on their own, but they’re responsible for coming back to the group with a solution.

    Being a facilitator also means admitting one’s mistakes and making peace with letting go of good ideas in the interest of moving the team forward. In the past I’ve made the mistake of coming to my team with a solution, and even when it was a good solution that action left them wondering what their role was aside from execution. This behavior is sometimes displayed by engineers who transition into management roles—that was certainly the case for me. Solving engineering problems was something I was comfortable doing and it was (and sometimes still is) easy to fall into wanting to overstep in the interest of being helpful. That’s a great way to waste talented minds and prevent people from learning. This isn’t limited to new engineering managers though—I’ve observed this behavior at various levels of leadership.

    These days I’m better at making space for others to thrive, and it comes from one simple question: am I coming to my team with a solution or a problem? I’m still a problem-solver at heart, so when I do have a suggestion I make sure to frame it as an option, one option, something to consider and not a prescription. Of course, sometimes I do need to step in, and it’s a fine line, but I try to do so by asking questions to ensure we consider key problems we need to solve. That’s because, as always, it’s about the problems, not the solutions.

  • Delivering Difficult News

    Earlier this year I was asked if some engineers from our team could help build a newly prioritized project. Doing so meant putting our team’s current work at substantial risk of failure. I knew canceling our project was the right thing to do because the incoming request was more important and because continuing our current work meant spreading our team too thin. Our team had put in a lot of time so I wanted to share the news empathetically, but delivering and receiving difficult news is never easy.

    I started by talking to my product manager. They quickly understood and helped me plan how we would deliver the news to our team. We drafted a list of points to communicate. Having a list was important because we didn’t want to leave out part of the message when speaking in different places or with different people. We detailed what was changing, why, what would happen next, and honestly that we made the decision. It was important to include we made the decision to avoid an “us vs. them” situation with the people who asked for our help. We shared the plan with our managers, then moved on to telling the team. We wanted to tell them as soon as possible out of respect and so they wouldn’t hear an incomplete message through some other channel.

    Having drafted the message we scheduled a 15 minute chat with each person on the team. These meetings took a full day and this process was slower than telling the team all at once, but it gave them a place to ask questions openly without the pressure that might come with speaking up in a bigger meeting. We took two or three minutes to share the message, and spent the rest of the time answering their questions earnestly. We gladly spent more time with people who had additional concerns as we wanted them to know we cared about them and their work. One person requested a retrospective, which we committed to having later in the week.

    Later that day we had a broader team meeting where we repeated the news the same way and answered questions from the group. This let the team settle into the decision together. By now the news wasn’t new to anyone, which meant the collective response was not one of shock. The people that initially requested our help attended the meeting so they could answer questions and explain why the new project was important for our users and product. And of course we held a retrospective later in the week, as promised.

    After the team meeting we sent a brief email with the same message to our team and adjacent people working with us. The idea was to ensure people who couldn’t attend earlier meetings received the message.

    While our team was disappointed that we cancelled the project, they understood the circumstances and took the news well. More than one person told us they appreciated how we communicated the cancellation. We helped with the other project, and worked to deliver a new, well received, project from our team some time later.

    It’s worth planning how you communicate difficult news. Our approach was to be honest, clear, and consistent. We were quick to communicate, accountable for the decision, gave everyone a chance to ask questions in a low pressure environment, repeated the message consistently in more than one place, and gave people multiple chances to share their concerns. We then worked to address them where we were able to do so.

    Follow @amdev on Twitter for occasional management and engineering thoughts, but mostly puns and pizza.

  • It’s About Managing Expectations

    Some time back my team was asked to take up a critical project on short notice. My product manager and I found out about the project on Monday evening and we were told it needed to ship on Friday the same week to 3 client platforms with a small team. We had just four days to make this happen and the date couldn’t move so we had to work with the other two project levers: changing the amount of work we would deliver, and putting more people on the project. The latter famously only helps so much. I want to share how we got many people on the same page and kept them there by managing their expectations.

    What are expectations?
    Expectations are things people believe will happen in the future. Unmet expectations can lead to unfortunate outcomes like duplicate effort, missed opportunities, or worse. Managing expectations is the act of making promises you intend to keep, making sure other people are on board with the plan throughout the process, and bringing them along when things change. Managing expectations helps us avoid unfortunate outcomes, sets a baseline for accountability, and builds trust that can lead to new opportunities.

    Planning
    The first step my PM and I took was to get each other on the same page. That night we discussed our approach and how we’d divide the work. We also decided we would not block on each other when making decisions. This helped us move more quickly and cover for each other when issues came up. We wrote a plan early Tuesday morning detailing our recommended approach and the timeline on which we would deliver the project. We planned just one and a half days for engineering and half a day for QA. That meant we’d ship Friday on one platform and branch for release on the two others that required a longer release process. We chose this approach so we could understand the effect of our change in one market before going global with it. This gave us confidence and helped us get buy-in from a bunch of folks. We deliberately chose the smallest amount of work we could do that would meet our goals. We also let our team know something disruptive was coming and that we’d share more as soon as we could.

    Our plan included other solutions we considered and why we had ruled them out. We did this to avoid spending time going back and forth negotiating what we’d build. In this case our primary stakeholders were a few decision makers farther up and peers from other teams, some of which our change might affect. We made sure we had the go-ahead from the former, but later found out two peers had concerns about the plan. Given the tight timeline we’d gone for the simplest option, which was to turn off their unshipped features temporarily when our temporary change was enabled. We talked with our peers and shared our constraints with them, then agreed to keep their features enabled on a best-effort basis. Their features were complimentary so this was the right thing to do in the end.

    My take-away from this was it’s important to check my assumptions about the expectations others have of me and my team because approval from decision makers isn’t the same as a mandate. I now ask myself what expectations I have of others, and what expectations they have of me and I try to answer those questions before moving forward.

    Sometimes these expectations are clear, but what if they haven’t been said aloud and agreed upon? In my experience this ambiguity leads to misaligned expectations and in turn the sort of unfortunate outcomes mentioned above. I address this by asking questions that might seem silly in my mind and by stating my understanding of our shared expectations aloud. I’ve found others often have the same questions so they end up being not so silly after all. When stating shared expectations I talk about next steps, who is accountable for them, timelines, and expected communication cadence. This gives others a chance to correct our shared understanding. I also ensure decision makers are explicitly onboard with the plan and timeline, not just initially but also throughout the product process, even if the plan does not change. This is important because even if my team’s plan doesn’t change, the plans of adjacent teams or the company might change, and that could affect the relative priority of my work. Another mistake I’ve made in the past is assuming a lack of response to an update was the same as continued approval. I now like to ask for explicit confirmation or feedback on updates so I can work to address concerns if needed.

    With everyone on the same page, we shared the plan with our engineering team and asked if they could meet the timeline. This was mid-day on Tuesday. It was critical to have their okay on the plan because they were the ones that would make the changes we had in mind. They said yes, and we told our stakeholders we were good to go. We were now accountable for the work and the timeline. This wasn’t about taking blame if we failed so much as having an understanding of our goals, and knowing we needed to share progress and roadblocks along the way. We gave regular updates on our progress, which in this case was at least twice a day given the nature of the work. We also shared new decisions we made knowing we could adjust later if needed.

    Managing Expectations
    We’ve talked about how to get everyone on the same page, but we haven’t talked about how to keep them there. That entails communicating with people based on their role with some regularity. Here I ask myself what information the people I’m communicating with need to do their job and how often they need it. In other words, it’s not enough to “manage upward” — you need to manage communication in every direction.

    For instance, a high level decision maker likely doesn’t need to know every detail of my team’s project, but they probably want to understand whether progress is being made, whether it will ship when they expect, and if we need help. Our updates to decision makers on this project were on the order of a few bullet points, which we followed by answering any questions they had. Peers might expect more detailed updates relevant to their role.

    I mentioned we shared updates at least twice a day on this project, but communication cadence depends on the circumstances. I think about sharing updates at a cadence that keeps my work top of mind, but not so often that I’m not conveying meaningful progress. The idea is to make sure stakeholders don’t feel out of touch with your work. Knowing you’re making progress also helps when it comes time for them to shelve projects that aren’t working out.

    You made it all the way to the end of this post so I suppose you want to know how the story ended: we shipped on time with happy stakeholders and our change met the goals we set in our plan.

    Follow me on Twitter if you like puns, pizza, and performance reviews.

  • Software Engineering Isn’t Magic

    The other day I was chatting with a friend who is both very competent and early in their software engineering career. They had noticed it sometimes took others less time to solve problems than it took them, and when they’d ask for help, these folks knew the answer or could often point them in the right direction right away. It felt like magic.

    Thinking this is normal, but it’s not magic. When people come back with an answer quickly it’s because they’re aware of the problem, already understand how a system works, or because they’re pattern matching against previous experiences and happened to guess the issue. But if you’ve ever handed off a difficult bug only to have a peer come back the next day with what seems like a simple fix, it can trigger negative feelings.

    A couple of years ago I was trying to understand a strange bug in my company’s app that broke the theming system with a new beta release of iOS. After several hours I was able to figure out where our theming code now failed, but I was stuck trying to find the root cause of the bug. I asked the person who wrote the theming system for help and they came back the next day with a single, subtle observation about a behavior change in the underlying system libraries that allowed them to fix the problem. In this moment software felt like magic, but in reality this person had invested a tremendous amount of time learning the underlying system libraries. They’d spent hours debugging the problem to understand it. That isn’t magic — that is expertise and putting in the time.

    A year or two before that I was helping improve build times for our team when I hit a segmentation fault in a popular compiler. I remember feeling helpless until a more experienced engineer said something like “it’s just software and we can debug it like anything else[^1].” Ah, just because it’s a black box doesn’t mean it’s magic. It’s a system we can debug because we can observe its inputs and outputs. That gave me the boost I needed to go back to basics: I started commenting out code to see where the compiler failed. After a while I was able to pinpoint the problem (two particular operator overloads) and found a solution that let us roll out a major reduction in full and incremental build times.

    It’s okay for you to take longer than others to find problems or fix things, and it’s okay to need help from others. You’ll get better at what you do over time too. They’re not faster because they work magic — they’ve just seen this before. Sometimes you’ll see something that others haven’t seen, and suddenly you’ll be the magician.

    Thanks to my friend Joe (@mergesort) for helping me edit this post. You should follow him on Twitter. Oh, and I’m on Twitter as @amdev as well.

    [^1]: They meant literally build and debug the compiler

  • Thoughts on Teams

    People have different value systems. Someone may value very expensive headphones, or cameras and you may value sneakers. Someone may value working really hard, and you may value having time to live and travel. It’s best to not push your value system onto others, because it can harm relationships. It’s okay and good that people value different things.

    Related to value systems — how does one’s value system affect how they weigh tradeoffs? Is this refactor more or less important than shipping? Internationalization? Accessibility? Polish? What stage is your company at? What’s the right thing to do? How does one’s value system help define “right”? Can you think of a way to acknowledge a peer’s values and leverage strengths in a way that helps your team succeed and makes them happy?

    Value systems also affect motivations, which vary from person to person. Some people are motivated by public praise, or maybe the rush of shipping something new and exciting, knowing they built something really well, realizing a lot of people will use their work, just helping others, money, or most likely a combination of these things and others. It’s hard to change people’s motivations, but understanding them can help you improve your team’s performance as you work to fulfill them on an individual level. (The team’s needs should come first most of the time, of course.)

    Working hard at your job will sometimes net you rewards, but working hard alone isn’t enough. You have to work on the right things — generally the right things end up being whatever is important to the people making decisions. In my experience it’s easier to burn out working hard on the wrong things, but it can also be really gratifying serving unfulfilled needs. The lesson here is to think about your goals before working hard and choose the right things to work on based on those goals and what you value.

    Be kind, but also direct with others in business to avoid misunderstandings. Sort of related — don’t be a jerk or you’ll harm relationships and people won’t want to work with you.

    Try to be “on” at work. Make a todo list (however you like), and do either the most important thing or the thing you have time to do right now. Mark it off. Remove things that stay on the list for too long without progress and aren’t actually critical.

    Let people know where you’re at regularly and set expectations on when you can get back to them with what they need. This is a lot of what it takes to succeed. Once you’re good at your core responsibilities, look for problems that get in the way of your team’s success. Set out to fix those issues, and get help from your peers. Share credit with and publicly thank the people who help. You’ll build trust and get recognition. Take a broader view of “team” over time and repeat this process to drive bigger change.

    Is this similar to your experience? Lemme know on Twitter.

    People have different value systems. Someone may value very expensive headphones, or cameras and you may value sneakers. Someone may value working really hard, and you may value having time to live and travel. It’s best to not push your value system onto others, because it can harm relationships. It’s okay and good that people value different things.

    Related to value systems — how does one’s value system affect how they weigh tradeoffs? Is this refactor more or less important than shipping? Internationalization? Accessibility? Polish? What stage is your company at? What’s the right thing to do? How does one’s value system help define “right”? Can you think of a way to acknowledge a peer’s values and leverage strengths in a way that helps your team succeed and makes them happy?

    Value systems also affect motivations, which vary from person to person. Some people are motivated by public praise, or maybe the rush of shipping something new and exciting, knowing they built something really well, realizing a lot of people will use their work, just helping others, money, or most likely a combination of these things and others. It’s hard to change people’s motivations, but understanding them can help you improve your team’s performance as you work to fulfill them on an individual level. (The team’s needs should come first most of the time, of course.)

    Working hard at your job will sometimes net you rewards, but working hard alone isn’t enough. You have to work on the right things — generally the right things end up being whatever is important to the people making decisions. In my experience it’s easier to burn out working hard on the wrong things, but it can also be really gratifying serving unfulfilled needs. The lesson here is to think about your goals before working hard and choose the right things to work on based on those goals and what you value.

    Be kind, but also direct with others in business to avoid misunderstandings. Sort of related — don’t be a jerk or you’ll harm relationships and people won’t want to work with you.

    Try to be “on” at work. Make a todo list (however you like), and do either the most important thing or the thing you have time to do right now. Mark it off. Remove things that stay on the list for too long without progress and aren’t actually critical.

    Let people know where you’re at regularly and set expectations on when you can get back to them with what they need. This is a lot of what it takes to succeed. Once you’re good at your core responsibilities, look for problems that get in the way of your team’s success. Set out to fix those issues, and get help from your peers. Share credit with and publicly thank the people who help. You’ll build trust and get recognition. Take a broader view of “team” over time and repeat this process to drive bigger change.

    Is this similar to your experience? Lemme know on Twitter.

  • Resolving Conflict with Empathy

    Note: I wrote the following note over a year ago, but only recently decided to send it to my newsletter. I’m not sure why I sat on it so long. Your [respectful] thoughts are welcome on Twitter.

    Empathy doesn’t always come easy, but it goes a very long way toward building trust with those around you. I’ve seen many well-intentioned people lose the trust of their peers because they fail to communicate with empathy. Perhaps they rush when replying to a message, or they fail to set aside frustration with a decision they believe should have gone a different way. In many cases this is a matter of differently weighed trade offs or a misunderstanding about who is empowered to make a decision. It turns out these things are sometimes unclear.

    It’s important to take a step back in moments like these to get a broader view of the situation: try to understand the position the other person is in, their priorities, how they may be weighing trade offs differently from you, whether they have context you don’t, and who should make the decision. Afterward, think about how to move the conversation forward in a way that’s constructive in the short and long term. Be direct, but diplomatic and try to address their concerns directly.

    I understand using light gray for the text color here is important to you because it matches our logo, but it’s really important that the contrast is high so people with colorblindness can read it.

    This example is intentionally low key — let’s set that aside. We start by showing empathy for our colleague’s point of view. We tell them we understand their concern and attempt to repeat it. This gives them a chance to correct us if there’s a misunderstanding on our part. We then share our proposed decision and the explicit trade offs we’ve made. In the first example above, we posited accessibility was more important than whether text color matches our logo color. Making implicit value judgements about trade offs explicit is key to building understanding.

    If we don’t understand the concern then we ask clarifying questions.

    I want to make sure I understand your concern. Are you worried about whether the text color matches the logo, or are you generally worried about brand guidelines? Is it something else?

    Earlier I said to be “constructive in the short and long term” but what I really mean is if you treat people poorly then they will not want to work with you. If you do this repeatedly then you’ll have a bad reputation and, in turn, your effectiveness will go down over time as more teammates view working with you as a burden.

    You might have been told to “disagree and commit” at some point in your career. Knowing when to fold ‘em, as they say (whoever “they” are), is an important tool in maintaining trust long term and maximizing your effectiveness at work. When deciding whether you should disagree and commit you should consider the long and short term ramifications of a decision[1] and whether you’re the person empowered to make the decision. For instance, if the decision is regarding something like the naming of an internal API then arguing until you get your way is going to erode some amount of trust from your teammate(s). Over time situations like this will cause you to be seen as an inflexible person. On the other hand, advocating for something like maintaining or increasing user privacy is likely really important and is worth fighting for.

    You might also be asked to consult on a decision. This might come in the form of a friend or colleague asking for advice, or some sort of group setting that’s more formal. It’s important to recognize your place in these situations. Are you being asked to give input or are you the decision maker? It’s important to set your expectations accordingly to avoid disappointment if the designated decision maker makes a decision in conflict with your advice. If you don’t understand the decision then ask what trade offs they made or what additional context and inputs they took into account. Remember that they may weigh trade offs differently from you and that’s okay.

    If you’re a decision maker asking others for advice, then set their expectations early. Some people may need you to tell them their input is valuable and appreciated, but you are also taking many inputs into account in some broader scope. These people may also need to be told directly that you may make a decision counter to their recommendation if other inputs need to be weighed more heavily. This approach can help avoid needless escalation and conflict.

    Resolving conflict is more difficult if there’s not a well-defined decision maker. For example, consider the case where you and a peer have conflicting viewpoints on a decision and you’re at a stalemate after going through the process. In cases like this it’s helpful to bring in a third party to mediate or break the tie. It might also be helpful to consider whether the decision is reversible. If it is, then it’s easier to disagree and commit to move forward. If it doesn’t work out the team can take its lumps and change course. Most decisions are reversible.

    A final note: it’s sometimes quite hard to strike the right balance between directness and respectful tone in text. Try talking to your counterpart face to face or video chat if a conversation goes sideways via text chat. It may remind you both there’s a human on the other side of the discussion.

    [1] It’s important to keep ethics in mind here. If you’re fighting to uphold some ethical boundary then it’s always worth continuing to advocate for your position.

  • Finding Dependency Cycles in iOS Builds with llbuild UI

    Did you know the swift-llbuild project contains a debugging tool called llbuild-ui that can find dependency cycles in your iOS build? To try it:

    1. Enable the New Build System (In Xcode it’s under File, Workspace Settings)
    2. Build your project
    3. Open the Derived Data directory on your machine and find the build.db file generated by the build system. This will be in a path similar to /Users/<yourUsername>/Library/Developer/Xcode/DerivedData/iOS-<someIdentifier>/Build/Intermediates.noindex/XCBuildData/build.db. Take note of this path as you will need it later.
    4. Follow the instructions here to install llbuild UI. You’ll need to have Python (and probably some dependencies) installed on your system.
    5. Start llbuild UI by running the command shown on the Github page linked in step 4 above in terminal. FLASK_APP=llbuildui.app venv/bin/python -m flask run
    6. Visit http://127.0.0.1:5000/ in your browser. You should see something like this:
    7. Click on Database Browser, which will show you the database configuration screen:
    8. Paste the path from step 2 above into the field and click “Set Path”
    9. Wait until the page refreshes then change the address in your browser’s address bar to http://127.0.0.1:5000/db/diagnostics. Any cycles found in your build should be shown on screen.
  • Diagnosing “Dependency Cycle Between Targets” in Xcode 9’s New Build System

    Xcode 9 includes a new build system that can substantially improve build times. The new build system is more strict about build issues, but some of its diagnostic output can be difficult to reason about. One difficult to debug error is “Dependency cycle between targets,” which may appear during incremental builds where you may not have an explicit circular build dependency between targets. Let’s talk about how these types of cycles are introduced and how to troubleshoot and fix them.

    There is more than one way to cause this sort of target dependency cycle without adding an explicit target dependency. The behavior of the legacy build system is to continue to (re)build instead of failing. This can increase incremental build times for your project, but the build ultimately succeeds and you may not know there’s a problem. The new build system fails instead, which helps you better maintain your project and means better incremental build times once you’ve fixed the dependency issues.

    During a full (clean) build the compiler will emit a list of dependencies for each compilation unit in your project. On subsequent (incremental builds), this data is used by the build system to help determine what parts of your project need to be rebuilt depending on what has changed since the last build. This list may include implicit dependencies (not to be confused with the per-scheme setting!) picked up by the compiler.

    First, set this flag to get a bit more diagnostic info in the build log:

    defaults write com.apple.dt.XCBuild EnableDebugActivityLogs YES
    

    Reproduce the dependency cycle error and expand the related error message in the build log. Usually there’s a file name in the log. Open that file and make sure its imports are correct. For instance, if a library B is consumed by library A and library B also #imports a file in library A then there’s a circular dependency. Remove the offending import and build again.

    There’s at least one more way to introduce implicit dependency cycles. For instance, modulemaps within header search paths will be added as dependencies for a given file automatically. In fact, a file may introduce a dependency cycle with its own module if the (e.g. static library manually specifying a modulemap) target containing the file has a modulemap in its Headers build phase and that phase is placed after the Compile Sources build phase. This can be resolved by dragging the Headers phase above the Compile Sources phase in the Build Phases subtab of your target’s settings in Xcode.

    If this doesn’t work then it’s time to dig a bit deeper. You can see a list of dependencies (found by the compiler) for a given file by inspecting the contents of <filename>.d in ~/Library/Developer/Xcode/DerivedData/<project-name>-<some-identifier>/Build/Intermediates.noindex/<name>.build/Debug-<target-device>/<name>.build/Objects-normal/<arch>/ after performing a build.

    Look for modulemaps that don’t belong in the dependency list. For instance, if the file is in library A and library A’s target doesn’t depend on libraries B and C, but B and C’s modulemaps appear in the dependency list then library A’s header search paths are likely misconfigured. Clean up library A’s the header search paths and try again.

    This is probably not an exhaustive list of ways to cause and fix this error. You can let me know if you know more about this issue on Twitter.