kait.dev

I keep seeing the iPhone’s popularity and sales numbers thrown around as proto-defenses against allegations of flexing monopolistic power in one category to dominate others.

“Popularity” is an argument IN FAVOR of the the government, not a defense. The argument is that the iPhone is very popular and sold a lot, and Apple is using that position of strength to stifle innovation and hamper the growth of competitors in related categories (payments, apps, music services, etc.).

You can disagree with the suit all you want, just know what you’re arguing for and against.

I think this ultimately traces back to a weird implicit belief I’ve been noticing lately, which is like a Constitutional (or god-given) right to a certain business model. If you listen in the news, you can hear it being implied all the time.

Solutions come in all sizes. The problem in tech (and many other industries, I presume) is that our processes and workflows are structured in such a way that the solutions for a given problem tend to be clustered around the smaller side of the scale.

Consider any given bug. Reported (hopefully) by your QA team or, worst-case, by a customer in production, it points out a specific issue. You, the developer, are tasked with devising a solution. Now, in most shops you’ll be given the opportunity to work out the root cause, ensuring that whatever change you make will a) actually fix the problem, and b) not cause any other immediate problems.

And that makes sense, for the most part. Small issues have small solutions. The problem is when you don’t step back and take a bigger-picture view of the situation - do all of these disparate problems actually stem from a particular source? Very often, developers are not only encouraged but actually mandated to stick to whatever story they’re on, for fear of going out of scope.

While that might make sense from a top-down control perspective, that style of thinking tends to permeate a lot of the other work that gets done, even up to larger-scale issues. Diversity is left to HR, or to a diversity committee, to take care of. In many cases, how and where to include AI in an application is left up to individual departments or teams. Remote work, a topic extremely divisive of late, is being eliminated or limited left up to “manager discretion” rather than actually looking at the benefits and harms that are associated with it. A cause extremely close to my heart, accessibility, is frequently treated as an add-on or left up to a handful of specialists to implement (or, worse, a third-party plugin).

These things not only don’t have to, they shouldn’t be left up to small groups to implement or reason through. They should be baked-in to how your organization makes decisions, builds software and interacts with its people.

You need a holistic approach. I want to break these concepts out of silos. If we're looking at a RACI chart, everyone is responsible for DEIB and accessibility. Everyone should be consulted and accountable for decisions about AI and remote work.

Now, I have a confession. I'm pretty sure it's Steve Jobs’ Second Law of Product that any time you think you have an insight, you have to give it a fancy name. I am guilty of this as well. 

I use the term “holistic tech” to talk about the convergence of these ideas. A lot of the specific things I'm talking about can be found in other systems or methodologies; I'm just trying to pull all the threads together so we can hopefully weave something useful about it. In the same way that responsive design was concerned with making sure you could use a product across all screen sizes, I want to make sure that (and here's the subtitle) tech works for everybody.

I'm also gonna borrow some concepts from universal design. Universal design is the concept that, "the design and composition of an environment so that it can be accessed, understood and used to the greatest extent possible by all people regardless of their age, size, ability or disability."

And last, we'll also fold in some concepts of human-centered design. This, in a nutshell, is thinking beyond your optimal user story. Eric Meyer calls them "stress cases,"  as opposed to edge cases, where you consider the emotional, physical and mental state of your user, rather just concerning yourself with the state of your application. 

But all of these, as implied with the word "design," are focused primarily on product creation. And while I do want to incorporate that, it's a part of how we work.

Basically, this whole idea boils down to a single word

EMPATHY

It's about seeing other people as, well, people.  

And it's applicable up and down your company stack. It applies to your employees, your boss, your monetization strategy (specifically, not using dark patterns), and it's especially about your communication, both within your organization and with your users.

As for product design, we'll start with accessibility.

Very broadly, accessibility is concerned with making sure that everyone can access your content and product. On the web side of things, this typically is accomplished by trying to adhere to the Web Content Access Guidelines, or WCAG. 

WCAG has four basic principles:

  • The first is that content should be perceivable, which relates to multi-sensory content and interfaces. Essentially, you should still be able to access the fundamental value of the content even if you cannot engage with its primary medium; the common examples here are alt text for images or captions for videos.

  • The second principle is operable: Users must be able to operate user interface controls in multiple modalities. The most common example of this is keyboard navigability; there are several requirements around people being able to video controls or manipulate modals without using the mouse (or touch).

  • The third principle is understandable: Text needs to be readable and understandable, and user interface elements should behave in predictable ways. Headers should always act like headers.

  • The last principle is robustness, which amounts to future-proofing. Make sure you adhere to the specs so that future products that are trying to parse your content know they can do so in a coherent manner.

Now the interesting thing is, I don't think many people would object to those principles in, well, principle. They seem pretty common-sensical? "I want people to be able to access my content" is a fairly unobjectionable statement. The problem is that most organizations don't have a good sense for accessibility yet, so the projects are designed and budgeted without the specific accessibility implementations. Then, when it gets brought up, making the change would be "too expensive," or it would "take too long."

"And besides, it's an insignificant part of our market anyway."**** I cannot tell you how many times I've heard this argument. Whether it's an intranet ("we don't have that many disabled people working here") or an internal training video (“there aren’t that many blind workers”) or a consumer-facing product ("we're willing to live without that tiny part of the market"), there's a sense that accessibility is only for a very small subset of the population.

My favorite group accessibility experiment is to ask people to raise their hand if they use an accommodation.

Then, I ask them to raise a hand if they wear glasses, contacts, or a hearing aid.Or if they don't keep your monitor at full resolution ("less space," on Macs). Or if they ever change their  browser's or IDE's zoom level.

Those are all accessibility accommodations.

Because the truth of the matter is, we're all just temporarily abled. I don’t ask for hands on this one don't, but I’ll often ask if anyone’s ever bought something on eBay while drunk. Formally speaking, you are technically operating with a cognitive impairment when you bought that giant taco blanket on Amazon. And I'm willing to bet your fine motor skills weren't quite up their usual par, either.

Or maybe you sprained your wrist, or broke a finger. That's a loss of fine motor control that's going to make it more difficult to operate the mouse, even if only for a few weeks. Or how about any kind of injury or chronic pain that makes it painful to sit in a chair for long periods? Willing to bet after 4 hours you're not thinking as clearly or as quickly as you were during hour 1.

Some of these things, like neurodivergence or vision impairment or being paralyzed, can be permanent conditions. But just as many of them aren't. And it's important to keep that in mind, because even if your ideal user story is a 34-year-old soccer mom, chances are she's going to have some sort of cognitive impairment (lack of sleep, stress about kids) or processing difference (trying to juggle multiple things at the same time) or fine motor skills (trying to use your mobile app on the sidelines during December) at some point. So ignoring accessibility doesn’t just disenfranchise the “small” portion of your users who are visibly permanently disabled, it's making things more difficult for potentially all of your users at some point or another.

And as it turns out, adding accessibility features can actually grow your overall market share.

Imagine your first day at NewTube, the hottest new video app on the market. We're looking to change the world … by letting people upload and watch videos. I don’t know, venture capital! Anyway, the number of humans on the internet is 5.19 billion, so that’s our addressable market. We don’t need the microscopic share that would come from adding accessibility features.

Or do we?

Standard accessibility features for videos include text transcripts of the words spoken aloud in the video. The primary intention behind these is to ensure that those with hearing impairments can still understand what’s going on in the video. In a past job, proper captions cost somewhere in the range of $20+ per minute of video, though some products such as YouTube now have AI autocaptioning that’s getting pretty good. 

Another standard feature is an audio description track (and transcript). This is sort of like alt text for video –  it describes the images that are being shown on the screen, in order to make that information comprehensible to someone with visual impairments. 

My favorite example of this is the end scene from the movie Titanic. As a transcript, it looks like this:

[ music swells ]

ROSE: Uh!

[ splash ]

Audio description, on the other hand, would look something like this:

Present-day Rose walks to the bow of the research ship, which is deserted. Deep in thought, she climbs the railing and stares down at the water where the Titanic rests below. She opens one hand to reveal the Heart of the Ocean diamond. We flash back to 17-year-old Rose standing on the deck of the Carpathia, digging her hands into Cal’s overcoat and finding the diamond. Present day Rose shakes her head and, with a small gasp, sends the diamond to rest where it should have been some 80 years earlier.

I took some poetic license there, but that’s kind of the point of audio description – you’re not a court reporter transcribing what’s being said, you’re trying to convey the emotion and the story for those who can’t see the pictures. The transcript part isn’t technically a requirement, but since you typically have to write down the script for the AD track anyway, it tends to be included. To my knowledge, no one’s managed to get AI to do this work for them in any usable fashion.

Lastly, we have keyboard navigability. Being able to interact with and control the site just using a keyboard makes it easy for those without fine motor control (or who use screen readers) to easily find their way around.

Three features/feature sets. The first two are pretty expensive - we’ve either got to pay for or develop an AI service to write the transcriptions, or we have to make sure they’re available. Audio Descriptions are going to be a cost to us, regardless, and not a cheap one. Keyboard navigability could be built-in to the product, but it would be faster if we could just throw everything together in React and not have to worry about it. 

How much of an impact could it have on our audience?

Well, though only 2-3 children out of 1000 are born with hearing impairment, by age 18 the percentage of Americans who complain of at least partial hearing loss rises to about 15%. So if we don’t have captions, we’d better hope all our videos are Fail compilations, or we’re going to see some steep drop-offs.

When it comes to vision, it’s even worse. Approximately a billion people in the world have a vision impairment that was not prevented or has not been addressed. Even assuming significant overlap with the hearing impairment group, we’ll use 750,000,000, for a total of 10.8 percent.

And for inability to use a mouse, we’ll look at “overall prevalence of adults with a month of musculoskeletal pain related to a repetitive stress injury,” which isn’t nearly a large enough category to include everyone who might be navigating by keyboard, but is at 4%.

Which leaves us 70% of our addressable market, or 3.63 billion.

Now obviously these numbers are not exact. We’re very back-of-the-napkin here, but I would also argue that a real-world scenario could just as easily see our percentages of accommodation-seekers go up as down. The number of temporary cases of all of these items, the fact that first-world countries have higher prevalence of RSI (though much better numbers for vision impairment) mean that this 70% number is probably not as far away from reality as we think.

And even beyond people who need those accommodations, what about those who simply want them? 

My best friend watches TV with the captions on all the time because it’s easier for her to follow along, and she’s not alone. Netflix says 40% of global users watch with captions, to say nothing of public exhibitions like bars (where it’s often not legally permissible to have the sound on). 

Transcripts/audio descriptions are often HUGE boons to SEO, because you’re capturing all your content in a written, easily search-indexable format. 

And presumably you’ve used a video app on a TV. The app has already been designed to be used with directional arrows and an OK button - why not extend that to the desktop? You’ll notice the remote’s functionality is a subset of a keyboard, not a mouse. Boom, keyboard navigation.

So, to recap accessibility: Good for disabled users. Good for abled users. Good for business.**** And that’s the thing, taking a holistic approach to how we do tech should actually make everyone better off. It is the rising tide.

But let’s talk about the looming wave that overshadows us all. I speak, of course, of artificial intelligence. In the same way that software ate the world 15 years ago, and Bitcoin was going to replace all our dollars, artificial intelligence is going to eat all our software and all the dollars we software developers used to get paid. 

I want to make clear up front that I am not an AI doomsayer. I don’t think we’re (necessarily) going to get Skynetted, and if we are it’s certainly not going to be ChatGPT. Artificial intelligence in its current form is not going to enslave us, but I do think large swaths of the population will become beholden to it – just not in the same way. 

Similar to how algorithms were used in the 90s and 2000s to replace human decision-making, I think AI is going to be (ab)used in the same manner. We’ve all called in to a customer support line only to find that the human on the other end is little more than a conduit between “the system” and us, and the person can’t do anything more to affect the outcome than we can. 

With AI, we’re just going to skip the pretense of the human and have the AI decipher what it thinks you said, attempt remedies within the limits of what it’s been programmed to allow, and then disconnect you. No humans (or, likely, actual support) involved.

Is that the worst? Maybe not in all cases. But it’s also, in a lot of cases, going to allow these organizations to skip what should be important work and just let the AI make decisions. I’m much less concerned about SkyNet than I am the Paperclip Maximizer.

The paperclip maximizer is a thought experiment proffered by Nick Bostrom in 2003. He postulated that an AI given a single instruction, “Make as many paperclips as possible,” would/should end with the destruction of the entire earth and all human life. The AI is not given any boundaries, and humans might switch the machine off (thus limiting the number of paperclips), so the AI will eventually eliminate humans. But even if the AI thinks us benign, at some point the AI consumes all matter on the earth aside from humans, and we are just so full of wonderfully bendable atoms that could be used for more paperclips.

The “thought processes” of generative AIs, as currently constructed, are inherently unknowable. We know the inputs, and we can see the outputs when we put in a prompt, but we can’t know what they’re going to say - that’s where the special sauce “thinking” comes in. We try to control this by introducing parameters, or guidelines, to those prompts to keep them in line.

And I know you might think, “Well, we’ll tell it not to harm humans. Or animals. Or disrupt the existing socio-political order. Or …” And that’s actually a separate angle to attack this problem - humans not giving the proper parameters. At a certain point though, if you have to control for the entire world and its infinite varieties of issues, isn’t it easier to just do the work yourself? We’ve already got a lackluster track record in regard to putting reliable guardrails around AI, as the Bing Image Generator’s output so thoughtfully proves. 

One of the things computer nerds love to do more than anything is break new tech, and image generators are no exception. When it introduced a new image generation tool a while back, though Bing did restrict uses of the phrase “9/11" or “September 11,” it still allowed for image generations of “Spongebob flying an airliner into New York in 2000.” And of course, the most prominent image of New York in 2000 is likely going to include the World Trade Center.

Sure, Spongebob doing 9/11 is a brand hit to Nickelodeon and insulting to the victims’ families. But this is showing both failures - despite Bing’s overwhelming image consciousness that should have been baked into a model, the model thought it more important to generate this image than to not. And, separately, Bing failed to put proper safeguards into the system. 

So yes, the paperclips are a hyperbolic hypothetical, but if there’s one thing that capitalism has taught us it’s that there are companies out there who care more about the next dollar than anything else.

Businesses large and small make decisions based on weighing costs versus expected benefits of a given option all the time. Famously, with the Ford Pinto, one of the analyses Ford conducted cited the overall cost of redesigning fuel safety systems vs. the general cost to society of the fatal car crashes that might be spared. Because, to Ford, individual deaths were not thought of as particularly tragic. They were just numbers. It does not seem unreasonable to assume AI systems will be misused by those who are unscrupulous in addition to those who are just oblivious. 

In accessibility, most people think the cost of not being accessible is “well, how likely are we to get sued?” ignoring the benefits of people using the product more. With AI, this short-sighted calculus can come into play where “Oh, we’ll let the AI do it, and not have to pay a person!” Except, as we’ve pointed out, the AI probably isn’t very good and the cost comes in consumer goodwill.

And this doesn’t even touch things like source data bias, which is a huge issue in resume-reviewing AIs (whose datasets will cause the AI to be more likely to select for existing employees, exacerbating skewed hiring trends) and predictive policing algorithms (which exacerbate existing crime biases). 

Don’t forget you can now convincingly generate human-sounding responses in astroturfing campaigns or review spoofing, or empower scammers previously held back by non-native English suddenly sounding like every corporate communication (because AI’s probably writing those communiques, too).

Remember the part where I said I’m not an AI doomsayer? I’m really not! I think AI can be used in a lot of unique and interesting applications to make things better. We just need to be more judicious about how we employ it, is all. 

For example, in the medical field, there are numerous AI experiments around trying to find tumors from body scans; the AI is not notifying these people on its own, there are doctors who review flagged scans for closer examination. Or in drug trials, companies are using AI to imagine new shapes of proteins that will then have lots of trials and study before they’re ever put in a test subject. 

Using AI to generate advice that is then examined by humans for robustness is a great application of the tool. And sure, if Amazon wants to use AI to suggest product recommendations, I guess go ahead. It can’t be any worse than its current system of, “Oh, you bought a refrigerator? I bet you also want to buy several more.”

But that “generation” word is a sticking point for me. To the point of the job applicant winnowing, I have no problem with using quantitative questions to weed out applicants (do you have x years of experience, boolean can you work in the US), but I would hesitate to let a black-box system make decisions even as small as who should be considered for hiring based on inherently unknowable qualifications (as would be the case with the application of actual AI versus just algorithmic sifting).

And finally, just limit the use of generated content in general. Reaching back into my accessibility bag for a minute, there’s a class of images that per spec don’t need alt text: Images that are “purely” decorative and not conveying information. The question I always ask in such cases is: If the image is really providing no value to the user, do you really need it?

The same would go for unedited generated content. If you’re sending a communication that can be wholly generated by a computer, do you really need to send it? We’re taking the idea of “this meeting could have been an email” even further down the stack: Could that email in fact just be a Slack message, or better yet a reaction emoji? Just because you can expand your one-sentence idea more easily with AI doesn’t you have to or even should.

There’s likely a place for generated content, but it’s not anywhere near where we’re using it for now, with AI-generated “news” articles or advertising campaigns. It’s like when we just tried to add accessibility “with a button” - you cannot just throw this stuff out there and hope it’s good enough.

And I would hope it would go without saying, but please don’t replace therapists or lawyers or any other human who considers ethics, empathy, common sense or other essentially human traits with AI.

This is along the same lines as “generate advice not decisions,” - if you need to talk to the AI in order to be comfortable sharing things with a live person, that makes total sense. But don’t use the AI as a 1:1 replacement for talking to a person, or getting legal advice.

AI recap: Good for advice, not decisions. Good for assisting people, not replacing them (it’s a tool, not the mechanic). It can be good for business.

Now, I think at this point you can pretty much guess what I’m gonna say about remote work. And that’s good! Both because this is already long enough and because “holistic tech” is supposed to be a framework, not just specific actionable items. 

Remote work, of course, is the idea that you need not be physically present in a building in order to perform a job. Hybrid work is a mix of remote work with some time spent in the office. I’m not gonna try sell you hard on either option - but I will note that employees prefer flexibility and employers tend to enjoy the larger talent pool. But mostly, I want to talk about how to set up your organization for success in the event you choose one of them.

One of the issues when you have some people in the office and others who aren’t is the sense that employees in the office are prioritized above those who are remote. Some of this is understandable – if the company wants to incentivize people to come into the office by offering, for example, catered lunches once a week or something, I wouldn’t see that as something that those who aren’t attending are missing out on …. Unless they were hired as fully remote.

In my case, for example, company HQ is in Chicago; I live in Phoenix, Arizona. I was hired fully remote, and it would feel to me like I were a lesser class of employee if those in the Chicago area were regularly incentivized with free lunches when there’s no pragmatic way for me to partake. Luckily, our office uses a system where everyone gets the same amount of delivery credit when we have whole-office lunches, which allows all of us to feel included. 

Beyond incentives, though, is the actual work being done, and this is where I think some teams struggle. Especially when it comes to meetings, the experience of the remote attendee is often an afterthought. This can take the forms of whiteboarding (literally writing on a whiteboard off-camera in the room), crosstalk or side discussions that aren’t in listening range of the microphone, or showing something on a screen physically that’s not present virtually.

It’s not just “don’t punish your remote team members for being remote,” you’re actually hurting the organization as a whole. Presumably every member of the team was hired with an eye to what they can bring to the table; excluding them, or not giving them the full information, hurts everyone involved. 

And technological solutions for remote workers will benefit in-person workers as well! Talking into the microphone during meetings can help someone with cochlear implants hear better in the room just as much as it’ll help me sitting in my garage office 1200 miles away. Same goes for whiteboarding - having a Google Jam (is that where they’re called anymore? Bring back the Wave!) on their screen means my wife can actually follow along; if they have to read a whiteboard from even 14 feet away, they’ll lose track of what’s going on in the meeting.

Taking the time to plan for how the remote attendee’s experience helps everyone, and it’s not terribly difficult to do. You can even see it for yourself by simply attending the meeting from another room to give you perspective and help troubleshoot any issues. Part and parcel of this, of course, is investing in the tools necessary to make sure everyone can interact and collaborate on the same level.

It’s not all about managers/employers, though! Remote employees tend to think that remote work is just like being in the office, only they don’t have the commute. And while that’s true to some extent, there’s another crucial aspect that many of them are missing: Communication. 

You have to communicate early and often when you’re remote for the simple reason that no one can come check up on you. No one can look over your shoulder to see if you’re struggling, no one knows intuitively what your workload looks like if you’re overloaded. Similarly, you don’t know what impacts your coworker’s commit is going to have unless you ask them. There are any number of tools and video sharing apps and all that, but the upshot is you actually have to make focused efforts to use them to make sure everyone’s rowing in the same direction.

Remote work: good for employees, good for employers. Good for business.

Finally, let’s talk diversity. Commonly abbreviated DEI, or DEIB, diversity, equity, inclusion and belonging has sort of morphed from “let’s make sure our workforce looks diverse” to “let’s make sure people of different backgrounds feel like they have a place here.”

And that’s because DEIB should be a culture, not an initiative. At the start, we talked about silos vs. intersectionality. This might start with a one-off committee, or an exec hire, but true DEIB is about your entire culture. Just like remote work can’t just be HR’s problem, and AI decisions shouldn’t be made solely by the finance team, DEIB needs to come from the entire organization.

I actually like the addition of the B to DEI because Belonging is a pretty good shorthand for what we’ve been discussing throughout. People who are temporarily or permanently disabled are provided the accommodations they need to succeed and thrive; programmers aren’t worried AI is going to be used to replace them, but instead given to them as a tool to increase their productivity. Remote workers feel like the company values them even in a different state.

DEIB should encompass all those things, but it can’t be left up to just a committee or an exec or even a department to account for it. It all falls on all of us.

And I specifically don’t want to leave out the traditional aspects of diversity, especially in tech culture. Minorities of all kinds – women, nonbinary folks, other gender identities, those of different sexual orientations, non-white racial backgrounds – are underrepresented in our industry, and it’s important that we keep up the work required to make sure that everyone is given the same access and opportunities. 

It’s good for business, too! Having a diverse array of perspectives as you develop products will give you ideas or user stories or parameters a non-diverse group might never have thought of. We keep hearing stories about VR headsets that clearly weren’t designed for people with long hair, or facial recognition algorithms that only work for those with lighter skin tones. If your product serves everybody, your product will be used by more people. That’s basic math!

Recent court rulings have put a damper on what used to be the standard for diversity, a “quota” of either applicants or hires meeting certain criteria. And look, if your organization was hiring just to meet a metric, you didn’t have true diversity. Quotas don’t create a culture of inclusion, so them going away shouldn’t cause that culture to dissipate, either. Seek out diverse upstreams for your hiring pipeline, ensure you’re not just tapping the same sources. I promise you, that investment will provide a return.

Say it with me: DEIB is good for employees, good for employers, and it’s good for business.

TLDR: Have empathy. Make sure you consider all aspects of decisions before you make them, because very often taking the personhood of the other party into account is actually the best business move as well. 

And with all of these, please note that when I say these are good for employees, good for employers and, especially, “good for business” requires these ideas to be executed well. Doing it right means taking as many of these factors into account as you can. This is where holistic tech comes in as our overarching concept.

  • When it comes to accessibility, the more you have, the more customers you can reach and the more options you give them. With a long lens, that tends to mean you wind up with more money. 

  • When you’re considering applications for artificial intelligence, try to keep its influence to advice rather than making decisions, and consider the work that would need to be done in order to implement the solution without AI – if it’s not work you’re willing to do, is it worth doing at all, AI or no? 

  • With remote work, you need to invest the time and resources to ensure your remote employees have the tools to communicate, while employees need to invest the time and energy to actually communicate.

  • Finally, diversity and belonging are about your culture, not a committee or a quota. Invest in it, and you’ll reap rewards.

I will begrudgingly admit that a 5,000+ word essay is not the most accessible form of this content for everyone. Guess you should just come to one of my talks!

OK, we need to talk about OREOs ... and how they impacted my view of product iteration.

(Sometimes I hate being a software developer.)

A package of Space Dunk oreos

I'm sure you've seen the Cambrian explosion of Oreo flavors, the outer limits of which were brought home to me with Space Dunks - combining Oreos with Pop Rocks. (And yes, your mouth does fizz after eating them.)

Putting aside the wisdom or sanity of whoever dreamt up the idea in the first place, it's clear that Oreo is innovating on its tried-and-true concept – but doing so without killing off its premier product. There is certainly some cannibalization of sales going on, but ultimately it doesn't matter to Nabisco because a) regular Oreos are popular enough that you'll never kill them off completely, and b) halo effect (your mom might really love PB oreos but your kid hates them, so you now you buy two bags instead of one!)

In software, we're taught that the innovator's dilemma tends to occur when you're unwilling to sacrifice your big moneymaker in favor of something new, and someone else without that baggage comes along eats your cookies/lunch.

Why can't you do both?

There are a number of different strategies you could employ, from a backend-compatible but disparate frontend offering (maybe with fewer features at a cheaper cost, or radically new UX). What about a faux startup with a small team and resources who can iterate on new ideas until they find what the market wants?

But the basic idea remains the same: Keep working away at the product that's keeping you in the black, but don't exclude experimentation and trying new approaches from your toolkit. Worst-case scenario, you still have the old workhorse powering through. In most cases, you'll have some tepid-to-mild hits that diversify your revenue stream (and potentially eat at the profit margins of your competitors) and open new opportunities for growth.

And every once in a while you'll strike gold, with a brand-new product that people love and might even supplant your tried-and-true Ol' Faithful.

The trick then is to not stop the ride, and keep rolling that innovation payoff over into the next new idea.

Just maybe leave Pop Rocks out of it.

I had the Platonic ideal of peanut butter pies at my wife's graduate school graduation in Hershey, PA, like five years ago. (They were legit Reese's Peanut Butter Pies from Mr. Reese himself.) I've chased that high for years, but never found it again. The peanut butter pie Oreos were probably the closest I've gotten.

If you rush and don’t consider how it is deployed, and how it helps your engineers grow, you risk degrading your engineering talent over time

I don't disagree that overreliance on AI could stymie overall growth of devs, but we've had a form of this problem for years.

I met plenty of devs pre-AI who didn't understand anything other than how to do the basics in the JS framework of the week.

It's ultimately up to the individual dev to decide how deep they want their skills to go.

You know it's a good sign when the first thing I do after finishing an article is double-check whether the whole site is some sort of AI-generated spoof. The answer on this one was closer than you might like, but I do think it's genuine.

Jakob Nielsen, UX expert, has apparently gone and swallowed the AI hype by unhinging his jaw, if the overall subjects of his Substack are to be believed. And that's fine, people can have hobbies, but the man's opinions are now coming after one of my passions, accessibility, and that cannot stand.

Very broadly, Nielsen says that digital accessibility is a failure, and we should just wait for AI to solve everything.

This gif pretty much sums up my thoughts after a first, second and third re-read.

A scene from Hot Fuzz, where Detective Andy leans down and says I got mad at literally the first actual sentence:

Accessibility has failed as a way to make computers usable for disabled users.

Nielsen's rubric is an undefined "high productivity when performing tasks" and whether the design is "pleasant" or "enjoyable" to use. He then states, without any evidence whatsoever, that the accessibility movement has been a failure.

Accessibility has not failed disabled users, it has enabled tens of millions of people to access content, services and applications they otherwise would not have. To say it is has failed is to not even make perfect the enemy of the good; it's to ignore all progress whatsoever.

The I will be the first to stand in line to shout that we should be doing better; I am all for interfaces and technologies that help make content more accessible to more people. But this way of thinking skips over the array of accessible technology and innovations that have been developed that have made computers easier, faster and pleasant to use.

For a very easy example, look at audio description for video. Content that would have been completely inaccessible to someone with visual impairments (video with dialogue) can now be understood through the presentation of the same information in a different medium.

Or what about those with audio processing differences? They can use a similar technology (subtitles) to have the words that are being spoken aloud present on the video, so they more easily follow along.

There are literally hundreds, if not thousands of such ideas (small and large) that already exist and are making digital interfaces more accessible. Accessibility is by no means perfect, but it has succeeded already for untold millions of users.

The excuse

Nielsen tells us there are two reasons accessibility has failed: It's expensive, and it's doomed to create a substandard user experience. We'll just tackle the first part for now, as the second part is basically just a strawman to set up his AI evangelism.

Accessibility is too expensive for most companies to be able to afford everything that’s needed with the current, clumsy implementation.

This line of reasoning is absolute nonsense. For starters, this assumes that accessibility is something separate from the actual product or design itself. It's sort of like saying building a nav menu is too expensive for a company to afford - it's a feature of the product. If you don't have it, you don't have a product.

Now, it is true that remediating accessibility issues in existing products can be expensive, but the problem there is not the expense or difficulty in making accessible products, it's that it wasn't baked into the design before you started.

It's much more expensive to retrofit a building for earthquake safety after it's built, but we still require that skyscrapers built in California not wiggle too much. And if the builders complain about the expense, the proper response is, "Then don't build it."

If you take an accessible-first approach (much like mobile-first design), your costs are not appreciably larger than ignoring it outright. And considering it's a legal requirement for almost any public-facing entity in the US, Canada or EU, it is quite literally the cost of doing business.

A detour on alt text

As an aside, the above image is a good example of the difference between the usability approach and the accessibility approach to supporting disabled users. Many accessibility advocates would insist on an ALT text for the image, saying something like: “A stylized graphic with a bear in the center wearing a ranger hat. Above the bear, in large, rugged lettering, is the phrase "MAKE IT EASY." The background depicts a forest with several pine trees and a textured, vintage-looking sky. The artwork has a retro feel, reminiscent of mid-century national park posters, and uses a limited color palette consisting of shades of green, brown, orange, and white.” (This is the text I got from ChatGPT when I asked it to write an ALT text for this image.)

On the other hand, I don’t want to slow down a blind user with a screen reader blabbering through that word salad. Yes, I could — and should — edit ChatGPT’s ALT text to be shorter, but even after editing, a description of the appearance of an illustration won’t be useful for task performance. I prefer to stick with the caption that says I made a poster with the UX slogan “Keep It Simple.”

The point of alt text is to provide a written description of visual indicators. It does NOT require you to describe in painstaking detail all of the visual information of the image in question. It DOES require you to convey the same idea or feeling you were getting across with the image.

If, in the above case, all that is required is the slogan, then you should not include the image on the page. You are explicitly saying that it is unimportant. My version of the alt text would be, "A stylized woodcut of a bear in a ranger hat evoking National Park posters sits over top of text reading "Make it easy.""

Sorry your AI sucks at generating alt text. Maybe you shouldn't rely on it for accessibility because it true accessibility requires ascertaining intent and including context?

The easy fix

Lolz, no.

The "solution" Nielsen proposes should be no surprise: Just let AI do everything! Literally, in this case, he means "have the AI generate an entire user experience every time a user accesses your app," an ability he thinks is no more than five years away. You know, just like how for the past 8 years full level 5 automated driving is no more than 2-3 years away.

Basically, the AI is given full access to your "data and features" and then cobbles together an interface for you. You as the designer get to choose "the rules and heuristics" the AI will apply, but other than that you're out of luck.

This, to be frank, sounds terrible? The reason we have designers is to present information in a coherent and logical flow with a presentation that's pleasing to the eye.

The first step is the AI will be ... inferring? Guessing? Prompting you with a multiple choice quiz? Reading a preset list of disabilities that will be available to every "website" you visit?

It will then take that magic and somehow customize the layout to benefit you. Oddly, the two biggest issues that Nielsen writes about are font sizes and reading level; the first of which is already controllable in basically every text-based context (web, phone, computer), and the second of which requires corporations to take on faith that the AI can rewrite their content completely while maintaining any and all style and legal requirements. Not what I'd bet my company on, but sure.

But my biggest complaint about all of this is it fails the very thing Nielsen is claiming to solve: It's almost certainly going to be a "substandard user experience!" Because it won't be cohesive, there have literally been no thought into how it's presented to me. We as a collective internet society got fed up with social media filter bubbles after about 5 years of prolonged use, and now everything I interact with is going to try to be intensely personalized?

Note how we just flat-out ignore any privacy concerns. I'm sure AI will fix it!

I really don't hate AI

AI is moderately useful in some things, in specific cases, where humans can check the quality of its work. As I've noted previously, right now we have not come up with a single domain where AI seems to hit 100% of its quality markers.

But nobody's managed to push past that last 10% in any domain. It always requires a human touch to get it "right."

Maybe AI really will solve all of society's ills in one fell swoop. But instead of trying to pivot our entire society around that one (unlikely) possibility, how about we actually work to make things better now?

More on this nonsense article

"I always love quoting myself." - Kait

We are also working directly with select AI companies as long as their plans align with what our community cares about: attribution, opt-outs, and control.

— Automattic news release

Oh yeah, all those AI companies that definitely care about attribution.

Between this and the Tumblr moderation nonsense, really seems like Matty is doing the dickish tech CEO speedrun. A modern-day Jason Russell.

Today I want to talk about data transfer objects, a software pattern you can use to keep your code better structured and metaphorically coherent.

I’ll define those terms a little better, but first I want to start with a conceptual analogy.

It is a simple truth that, no matter whether you focus on the frontend, the backend or the whole stack, everyone hates CSS.

I kid, but also, I don’t.

CSS is probably among the most reviled of technologies we have to use all the time. The syntax and structure of CSS seems almost intentionally designed to make it difficult to translate from concept to “code,” even simple things. Ask anyone who’s tried to center a div.

And there are all sorts of good historical reasons why CSS is the way it is, but most developers find it extremely frustrating to work with. It’s why we have libraries and frameworks like Tailwind. And Bulma. And Bootstrap. And Material. And all the other tools we use that try their hardest to make sure you never have to write actual while still reaping the benefits of a presentation layer separate from content.

And we welcome these tools, because it means you don’t need to understand the vagaries of CSS in order to get what you want. It’s about developer experience, making it easier on developers to translate their ideas into code.

And in the same way we have tools that cajole CSS into giving us what we want, I want to talk about a pattern that allows you to not worry about anything other than your end goal when you’re building out the internals of your application. It’s a tool that can help you stay in the logical flow of your application, making it easier to puzzle through and communicate about the code you’re writing, both to yourself and others. I’m talking about DTOs.

DTOs

So what is a DTO? Very simply, a data transfer object is a pure, structured data object - that is, an object with properties but no methods. The entire point of the DTO is to make sure that you’re only sending or receiving exactly the data you need to accomplish a given function or task - no more, no less. And you can be assured that your data is exactly the right shape, because it adheres to a specific schema.

And as the “transfer” part of the name implies, a DTO is most useful when you’re transferring data between two points. The title refers to one of the more common exchanges, when you’re sending data between front- and back-end nodes, but there are lots of other scenarios where DTOs come in handy.

Sending just the right amount of data between modules within your application, or consuming data from different sources that use different schemas, are just some of those.

I will note there is literature that suggests the person who coined the term, Martin Fowler, believes that you should not have DTOs except when making remote calls. He’s entitled to his opinion (of which he has many), but I like to reuse concepts where appropriate for consistency and maintainability.

The DTO is one of my go-to patterns, and I regularly implement it for both internal and external use. I’m also aware most people already know what pure data objects are. I’m not pretending we’re inventing the wheel here - the value comes in how they’re applied, systematically.

Advantages

  1. For DTOs are a systematic approach to managing how your data flows through and between different parts of your application as well as external data stores.

  2. Properly and consistently applied, DTOs can help you maintain what I call metaphorical coherence in your app. This is the idea that the names of objects in your code are the same names exposed on the user-facing side of your application.

Most often, this comes up when we’re discussing domain language - that is, your subject-matter-specific terms (or jargon, as the case may be).

I can’t tell you the number of times I’ve had to actively work out whether a class with the name of “post” refers to a blog entry, or the action of publishing an entry, or a location where someone is stationed. Or whether “class” refers to a template for object creation, a group of children, or one’s social credibility. DTOs can help you keep things organized in your head, and establish a common vernacular between engineering and sales and support and even end-users.

It may not seem like much, but that level of clarity makes talking and reasoning about your application so much easier because you don’t have to jump through mental hoops to understand the specific concept you’re trying to reference.

  1. DTOs also help increase type clarity. If you’re at a shop that writes Typescript with “any” as the type for everything, you have my sympathies, and also stop it. DTOs might be the tool you can wield to get your project to start to use proper typing, because you can define exactly what data’s coming into your application, as well as morphing it into whatever shape you need it to be on the other end.

  2. Finally, DTOs can help you keep your code as modular as possible by narrowing down the data each section needs to work with. By avoiding tight coupling, we can both minimize side effects and better set up the code for potential reuse.

And, as a bonus mix of points two and four, when you integrated with an external source, DTOs can help you maintain your internal metaphors while still taking advantage of code or data external to your system.

To finish off our quick definition of terms, a reminder that PascalCase is where all the words are jammed together with the first letter of each word capitalized; camelCase is the same except the very first letter is lowercase; and snake case is all lowercase letters joined by underscores.

This is important for our first example.

Use-case 1: FE/BE naming conflicts

The first real-world use-case we’ll look at is what was printed on the box when you bought this talk. That is, when your backend and frontend don’t speak the same language, and have different customs they expect the other to adhere to.

Trying to jam them together is about as effective as when an American has trouble ordering food at a restaurant in Paris and compensates by yelling louder.

In this example, we have a PHP backend talking to a Typescript frontend.

I apologize for those who don’t know one or both languages. For what it’s worth, we’ll try to keep the code as simple as possible to follow, with little-to-no language-specific knowledge required. In good news, DTOs are entirely language agnostic, as we’ll see as we go along.

Backend

class User { public function __construct( public int $id, public string $full_name, public string $email_address, public string $avatar_url ){} }

Per PSR-12, which is the coding standard for PHP, class cases must be in PascalCase, method names must be implemented in camelCase. However, the guide “intentionally avoids any recommendation” as to styling for property names, instead just choosing “consistency.”

Very useful for a style guide!

As you can see, the project we’re working with uses snake case for its property names, to be consistent with its database structure.

Frontend

`class User { userId: number; fullName: string; emailAddress: string; avatarImageUrl: string;

load: (userId: number) => {/* load from DB /}; save: () => {/ persist */}; }`

But Typescript (for the most part, there’s not really an “official” style guide in the same manner but most your Google, your Microsofts, your Facebooks tend to agree) that you should be using camelCase for your variable names.

I realize this may sound nit-picky or like small potatoes to those of used to working as solo devs or on smaller teams, but as organizations scales up consistency and parallelism in your code is vital to making sure both that your code and data have good interoperability, as well as ensuring devs can be moved around without losing significant chunks of time simply to reteach themselves style.

Now, you can just choose one of those naming schemes to be consistent across the frontend and backend, and outright ignore one of the style standards.

Because now your project asking one set of your developers to context-switch specifically for this application. It also makes your code harder to share (unless you adopt this convention-breaking in your extended cinematic code universe). You’ve also probably killed a big rule in your linter, which you now have to customize in all implementations.

OR, we can just use DTOs.

Now, I don’t have a generic preference whether the DTO is implemented on the front- or the back-end — that determination has more to do with your architecture and organizational structure than anything else.

Who owns the contracts in your backend/frontend exchange is probably going to be the biggest determiner - whichever side controls it, the other is probably writing the DTO. Though if you’re consuming an external data source, you’re going to be writing that DTO on the frontend.

Where possible, I prefer to send the cleanest, least amount of data required from my backend, so for our first example we’ll start there. Because we’re writing the DTO in the backend, the data we send needs to conform to the schema the frontend expects - in this instance, Typescript’s camel case.

Backend

class UserDTO { public function __construct( public int $userId, public string $fullName, public string $emailAddress, public string $avatarImageUrl ) {} }

That was easy, right? We just create a data object that uses the naming conventions we’re adopting for sharing data. But of course, we have to get our User model into the DTO. This brings me to the second aspect of DTOs, the secret sauce - the translators.

Translators

function user_to_user_dto(User $user): UserDTO { return new UserDTO( $user->id, $user->full_name, $user->email_address, $user->avatar_url ); }

Very simply, a translator is the function (and it should be no more than one function per level of DTO) that takes your original, nonstandard data and jams it into the DTO format.

Translators get called (and DTOs are created) at points of ingress and egress. Whether that’s internal or external, the point at which a data exchange is made is when a translator is run and a DTO appears – which side of the exchange is up to your implementation. You may also, as the next example shows, just want to include the translator as part of the DTO.

Using a static create method allows us to keep everything nice and contained, with a single call to the class.

`class UserDTO { public function __construct( public int $userId, public string $fullName, public string $emailAddress, public string $avatarImageUrl ) {}

public static function from_user(User $user): UserDTO { return new self( $user->id, $user->full_name, $user->email_address, $user->avatar_url ); } }

$userDto = UserDTO::from_user($user);`

I should note we’re using extremely simplistic base models in these examples. Often, something as essential as the user model is going to have a number of different methods and properties that should never get exposed to the frontend.

While you could do all of this through customizing the serialization method for your object. I would consider that to be a distinction in implementation rather than strategy.

An additional benefit of going the separate DTO route is you now have an explicitly defined model for what the frontend should expect. Now, your FE/BE contract testing can use the definition rather than exposing or digging out the results of your serialization method.

So that’s a basic backend DTO - great for when you control the data that’s being exposed to one or potentially multiple clients, using a different data schema.

Please bear with me - I know this probably seems simplistic, but we’re about to get into the really useful stuff. We gotta lay the groundwork first.

Frontend

Let’s back up and talk about another case - when you don’t control the backend. Now, we need to write the DTO on the frontend.

First we have our original frontend user model.

`class User { userId: number; fullName: string; emailAddress: string; avatarImageUrl: string;

load: (userId: number) => {/* load from DB /}; save: () => {/ persist */}; }`

Here is the data we get from the backend, which I classify as a Response, for organizational purposes. This is to differentiate it from a Payload, which data you send to the API (which we’ll get into those later).

interface UserResponse { id: number; full_name: string; email_address: string; avatar_url: string; }

You’ll note, again, because we don’t control the structure used by the backend, this response uses snake case.

So we need to define our DTO, and then translate from the response.

Translators

You’ll notice the DTO looks basically the same as when we did it on the backend.

interface UserDTO { userId: number; fullName: string; emailAddress: string; avatarImageUrl: string; }

But it's in the translator you can now see some of the extra utility this pattern offers.

const translateUserResponseToUserDTO = (response: UserResponse): UserDTO => ({ userId: response.id, fullName: response.full_name, emailAddress: response.email_address, avatarImageUrl: response.avatar_url });

When we translate the response, we can change the names of the parameters before they ever enter the frontend system. This allows us to maintain our metaphorical coherence within the application, and shield our frontend developers from old/bad/outdated/legacy code on the backend.

Another nice thing about using DTOs in the frontend, regardless of where they come from, is they provide us with a narrow data object we can use to pass to other areas of the application that don’t need to care about the methods of our user object.

DTOs work great in these cases because they allow you to remove the possibility of other modules causing unintended consequences.

Notice that while the User object has load and save methods, our DTO just has the properties. Any modules we pass our data object are literally incapable of propagating manipulations they might make, inadvertently or otherwise. Can’t make a save call if the object doesn’t have a save method.

Use-case 2: Metaphorically incompatible systems

For our second use-case, let’s talk real-world implementation. In this scenario, we want to join up two systems that, metaphorically, do not understand one another.

Magazine publisher

  • Has custom backend system (magazines)

  • Wants to explore new segment (books)

  • Doesn’t want to build a whole new system

I worked with a client, let’s say they’re a magazine publisher. Magazines are a dying art, you understand, so they want to test the waters of publishing books.

But you can’t just build a whole new app and infrastructure for an untested new business model. Their custom backend system was set up to store data for magazines, but they wanted to explore the world of novels. I was asked them build out that Minimum Viable Product.

Existing structure

`interface Author { name: string; bio: string; }

interface Article { title: string; author: Author; content: string; }

interface MagazineIssue { title: string; issueNo: number; month: number; year: number; articles: Article[]; }`

This is the structure of the data expected by both the existing front- and back-ends. Because everything’s one word, we don’t even need to worry about incompatible casing.

Naive implementation This new product requires performing a complete overhaul of the metaphor.

`interface Author { name: string; bio: string; }

interface Chapter { title: string; author: Author; content: string; }

interface Book { title: string; issueNo: number; month: number; year: number; articles: Chapter[]; }`

But we are necessarily limited by the backend structure as to how we can persist data.

If we just try to use the existing system as-is, but change the name of the interfaces, it’s going to present a huge mental overhead challenge for everyone in the product stack.

As a developer, you have to remember how all of these structures map together. Each chapter needs to have an author, because that’s the only place we have to store that data. Every book needs to have a month, and a number. But no authors - only chapters have authors.

So we could just use the data structures of the backend and remember what everything maps to. But that’s just asking for trouble down the road, especially when it comes time to onboard new developers. Now, instead of them just learning the system they’re working on, they essentially have to learn the old system as well.

Plus, if (as is certainly the goal) the transition is successful, now their frontend is written in the wrong metaphor, because it’s the wrong domain entirely. When the new backend gets written, we’re going to have to the exact same problem in the opposite direction.

I do want to take a moment to address what is probably obvious – yes, the correct decision would be to build out a small backend that can handle this, but I trust you’ll all believe me when I say that sometimes decisions get made for reasons other than “what makes the most sense for the application’s health or development team’s morale.”

And while you might think that find-and-replace (or IDE-assisted refactoring) will allow you to skirt this issue, please trust me that you’re going to catch 80-90% of cases and spend twice as much time fixing the rest as it would have to write the DTOs in the first place.

Plus, as in this case, your hierarchies don’t always match up properly.

What we ended up building was a DTO-based structure that allowed us to keep metaphorical coherence with books but still use the magazine schema.

Proper implementation

You’ll notice that while our DTO uses the same basic structures (Author, Parts of Work [chapter or article], Work as a Whole [book or magazine]), our hierarchies diverge. Whereas Books have one author, Magazines have none; only Articles do.

The author object is identical from response to DTO.

You’ll also notice we completely ignore properties we don’t care about in our system, like IssueNo.

How do we do this? Translators!

Translating the response

We pass the MagazineResponse in to the BookDTO translator, which then calls the Chapter and Author DTO translators as necessary.

`export const translateMagazineResponseToAnthologyBookDTO = (response: MagazineResponse): AnthologyBookDTO => { const chapters = (response.articles.length > 0) ? response.articles.forEach((article) => translateArticleResponseToChapterDTO(article)) : []; const authors = [ ...new Set( chapters .filter((chapter) => chapter.author) .map((chapter) => chapter.author) ) ]; return {title: response.title, chapters, authors}; };

export const translateArticleResponseToChapterDTO = (response: ArticleResponse): ChapterDTO => ({ title: response.title, content: response.content, author: response.author });`

This is also the first time we’re using one of the really neat features of translators, which is the application of logic. Our first use is really basic, just checking if the Articles response is empty so we don’t try to run our translator against null. This is especially useful if your backend has optional properties, as using logic will be necessary to properly model your data.

But logic can also be used to (wait for it) transform your data when we need to.

Remember, in the magazine metaphor, articles have authors but magazine issues don’t. So when we’re storing book data, we’re going to use their schema by grabbing the author of the first article, if it exists, and assign it as the book’s author. Then, our chapters ignore the author entirely, because it’s not relevant in our domain of fiction books with a single author.

Because the author response is the same as the DTO, we don’t need a translation function. But we do have proper typing so that if either of them changes in the future, it should throw an error and we’ll know we have to go back and add a translation function.

The payload

Of course, this doesn’t do us any good unless we can persist the data to our backend. That’s where our payload translators come in - think of Payloads as DTOs for the anything external to the application.

`interface AuthorPayload name: string; bio: string; }

interface ArticlePayload { title: string; author: Author; content: string; }

interface MagazineIssuePayload { title: string; issueNo: number; month: number; year: number; articles: ArticlePayload[]; }`

For simplicity’s sake we’ll assume our payload structure is the same as our response structure. In the real world, you’d likely have some differences, but even if you don’t it’s important to keep them as separate types. No one wants to prematurely optimize, but keeping the response and payload types separate means a change to one of them will throw a type error if they’re no longer parallel, which you might not notice with a single type.

Translating the payload

`export const translateBookDTOToMagazinePayload = (book: BookDTO): MagazinePayload => ({ title: book.title, articles: (book.chapters.length > 0) ? book.chapters.forEach((chapter) => translateChapterDTOToArticlePayload(chapter, book) : [], issueNo: 0, month: 0, year: 0, });

export const translateChapterDTOToArticlePayload = (chapter: ChapterDTO, book: BookDTO): ArticlePayload => ({ title: chapter.title, author: book.author, content: chapter.content });`

Our translators can be flexible (because we’re the ones writing them), allowing us to pass objects up and down the stack as needed in order to supply the proper data.

Note that we’re just applying the author to every article, because a) there’s no harm in doing so, and b) the system like expects there be an author associated with every article, so we provide one. When we pull it into the frontend, though, we only care about the first article.

We also make sure to fill out the rest of the data structure we don’t care about so the backend accepts our request. There may be actual checks on those numbers, so we might have to use more realistic data, but since we don’t use it in our process, it’s just a question of specific implementation.

So, through the application of ingress and egress translators, we can successfully keep our metaphorical coherence on our frontend while persisting data properly to a backend not configured to the task. All while maintaining type safety. That’s pretty cool.

The single biggest thing I want to impart from this is the flexibility that DTOs offer us.

Use-case 3: Using the smallest amount of data required

When working with legacy systems, I often run into a mismatch of what the frontend expects and what the backend provides; typically, this results in the frontend being flooded an overabundance of data.

These huge data objects wind up getting passed around and used on the frontend because, for example, that’s what represents the user, even if you only need a few properties for any given use-case.

Or, conversely, we have the tiny amount of data we want to change, but the interface is set up expecting the entirety of the gigantic user object. So we wind up creating a big blob of nonsense data, complete with a bunch of null properties and only the specific ones we need filled in. It’s cumbersome and, worse, has to be maintained so that whenever any changes to the user model need to be propagated to your garbage ball, even if those changes don’t touch the data points you care about.

An avatar of a man with a blue circle around his photo, with the name One way to eliminate the data blob is to use DTOs to narrowly define which data points a component or class needs in order to function. This is what I call minimizing touchpoints, referring to places in the codebase that need to be modified when the data structure changes.

In this scenario, we’re building a basic app and we want to display an avatar for a user. We need their name, a picture and a color for their frame.

const george = { id: 303; username: 'georgehernandez'; groups: ['users', 'editor'], sites: ['https://site1.com'], imageLocation: '/assets/uploads/users/gh-133133.jpg'; profile: { firstName: 'George'; lastName: 'Hernandez'; address1: '738 Evergreen Terrace'; address2: ''; city: 'Springfield'; state: 'AX'; country: 'USA'; favoriteColor: '#1a325e'; } }

What we have is their user object, which contains a profile and groups and sites the user is assigned to, in addition to their address and other various info.

Quite obviously, this is a lot more data than we really need - all we care about are three data points.

`class Avatar { private imageUrl: string; private hexColor: string; private name: string;

constructor(user: User) { this.hexColor = user.profile.favoriteColor: this.name = user.profile.firstName

  • ' '
  • user.profile.lastName; this.imageUrl = user.imageLocation; } }`

    This Avatar class works, technically speaking, but if I’m creating a fake user (say it’s a dating app and we need to make it look like more people are using than actually is the case), I now have to create a bunch of noise to accomplish my goal.

const lucy = { id: 0; username: ''; groups: []; sites: []; profile: { firstName: 'Lucy'; lastName: 'Evans'; address1: ''; address2: ''; city: ''; state: ''; country: ''; } favoriteColor: '#027D01' }

Even if I’m calling from a completely separate database and class, in order to instantiate an avatar I still need to provide the stubs for the User class.

Or we can use DTOs.

`class Avatar { private imageUrl: string; private hexColor: string; private name: string;

constructor(dto: AvatarDTO) { this.hexColor = dto.hexColor: this.name = dto.name; this.imageUrl = dto.imageUrl; } }

interface AvatarDTO { imageUrl: string; hexColor: string; name: string; }

const translateUserToAvatarDTO = (user: User): AvatarDTO => ({ name: [user.profile.firstName, user.profile.lastName].join(' '), imageUrl: user.imageLocation, hexColor: user.profile.favoriteColor });`

By now, the code should look pretty familiar to you. This pattern is really not that difficult once you start to use it - and, I’ll wager, a lot of you are already using it, just not overtly or systematically. The bonus to doing it in a thorough fashion is that refactoring becomes much easier - if the frontend or the backend changes, we have a single point from where the changes emanate, making them much easier to keep track of.

Flexibility

But there’s also flexibility. I got some pushback from implementing the AvatarDTO; after all, there were a bunch of cases already extant where people were passing the user profile, and they didn’t want to go find them. As much as I love clean data, I am a consultant; to assuage them, I modified the code so as to not require extra work (at least, at this juncture).

`class Avatar { private avatarData: AvatarDTO;

constructor(user: User|null, dto?: AvatarDTO) { if (user) { this.avatarData = translateUserToAvatarDTO(user); } else if (dto) { this.avatarData = dto; } } }

new Avatar(george); new Avatar(null, { name: 'Lucy Evans', imageUrl: '/assets/uploads/users/le-319391.jpg', hexColor: '#fc0006' });`

Instead of requiring the AvatarDTO, we still accept the user as the default argument, but you can also pass it null. That way I can pass my avatar DTO where I want to use it, but we take care of the conversion for them where the existing user data is passed in.

Use-case 4: Security

The last use-case I want to talk about is security. I assume some to most of you already get where I’m going with this, but DTOs can provide you with a rock-solid way to ensure you’re only sending data you’re intending to.

Somewhat in the news this month is the Spoutible API breach; if you’ve never heard of it, I’m not surprised. Spoutible a Twitter competitor, notable mostly for its appalling approach to API security.

I do encourage all of you to look this article up on troyhunt.com, as the specifics of what they were exposing are literally unbelievable.

{ err_code: 0, status: 200, user: { id: 23333, username: "badwebsite", fname: "Brad", lname: "Website", about: "The collector of bad website security data", email: 'fake@account.com', ip_address: '0.0.0.0', verified_phone: '333-331-1233', gender: 'X', password: '$2y$10$r1/t9ckASGIXtRDeHPrH/e5bz5YIFabGAVpWYwIYDCsbmpxDZudYG' } }

But for the sake of not spoiling all the good parts, I’ll just show you the first horrifying section of data. For authenticated users, the API appeared to be returning the entire user model - mundane stuff like id, username, a short user description, but also the password hash, verified phone number and gender.

Now, I hope it goes without saying that you should never be sending anything related to user passwords, whether plaintext or hash, from the server to the client. It’s very apparent when Spoutible was building its API that they didn’t consider what data was being returned for requests, merely that the data needed to do whatever task was required. So they were just returning the whole model.

If only they’d used DTOs! I’m not going to dig into the nitty-gritty of what it should have looked like, but I think you can imagine a much more secure response that could have been sent back to the client.

Summing up

If you get in the practice of building DTOs, it’s much easier to keep control of precisely what data is being sent. DTOs not only help keep things uniform and unsurprising on the frontend, they can also help you avoid nasty backend surprises as well.

To sum up our little chat today: DTOs are a great pattern to make sure you’re maintaining structured data as it passes between endpoints.

Different components only have to worry about exactly the data they need, which helps both decrease unintended consequences and decrease the amount of touchpoints in your code you need to deal with when your data structure changes. This, in turn, will help you maintain modular independence for your own code.

It also allows you to confidently write your frontend code in a metaphorically coherent fashion, making it easier to communicate and reason about.

And, you only need to conform your data structure to the backend’s requirements at the points of ingress and egress - Leaving you free to only concern your frontend code with your frontend requirements. You don’t have to be limited by the rigid confines backend’s data schema.

Finally, the regular use of DTOs can help put you in the mindset of vigilance in regard to what data you’re passing between services, without needing to worry that you’re exposing sensitive data due to the careless conjoining of model to API controller.

Honestly, I thought we were past this as an industry? But my experience at Developer Week 2024 showed me there's still a long way to go to overcoming sexism in tech.

And it came from the source I least expected; literally people who were at the conference trying to convince others to buy their product. People for whom connecting and educating is literally their job.

Time and again, both I (an engineer) and my nonbinary wife (a business analyst, at a different organization) found that the majority of the masculine-presenting folks at the booths on the expo floor were dismissive and disinterested, and usually patronizing.

It was especially ironic given one of the predominant themes of the conference was developer experience, and focusing on developer productivity. One of the key tenets of dx is listening to what developers have to say. These orgs failed. Horribly.

My wife even got asked when "your husband" would be stopping by.

I had thought it would go without saying, but female- and androgynous-presenting folk are both decision-makers in their own right as well as people with influence in companies large and small.

To organizations: Continuing to hire people who make sexist (and, frankly, stupid) judgments about who is worth talking to and what you should be conversing with them about is not only insulting, it's bad business. Founders: If you're the ones at your booth, educate yourselves. Fast.

I can tell you there are at least three different vendors who were providing services in areas we have professional needs around who absolutely will not get any consideration, simply because we don't want to deal with people like that. I don't assume the whole organization holds the same opinions as their representative; however, I can tell for a fact that such views are not disqualifying by that organization, and so I have no interest in dealing with them further.

Rather than call out the shitty orgs, I instead want to call out the orgs whose reps were engaging, knowledgable and overall pleasant to deal with. Both because those who do it right should be celebrated, and because in an attention economy any given (even negative) is unfortunately an overall net positive.

The guys at Convex**** were great, answering all my questions about their seemingly very solid and robust Typescript backend platform.

The folks at Umbraco**** gave great conference talks, plied attendees with cookies and talked with us at length about their platform and how we might use it. Even though I dislike dotNet, we are very interested in looking at them for our next CMS project.

The developer advocates at Couchbase**** were lovely and engaging, even if I disagree with Couchbase's overall stance on their ability to implement ACID.

The folks at the Incident.io**** booth were wonderful, and a good template for orgs trying to sell services: They brought along an engineering manager who uses their services, and could speak specifically to how to use them.

I want to give a shout-out to those folks, and to exhort organizations to do better in training those you put out as the voice of your brand. This is not hard. And it only benefits you to do so.

Also, the sheer number of static code analysis companies makes me thinks there's a consolidation incoming. Not a single one of three could differentiate their offerings on more than name and price.

“[Random AI] defines ...” has already started to replace “Webster’s defines ...” as the worst lede for stories and presentations.

I let the AI interview in the playbill slide because the play was about AI, but otherwise, no bueno.

Server for serverless

The way to guarantee durability and failure recovery in serverless orchestration and coordination is … a server and database in the middle of your microservices.

I’m sure it’s a great product, but come on.

The way to guarantee durability and failure recovery in serverless orchestration and coordination is … a server and database in the middle of your microservices.

I’m sure it’s a great product, but come on.

I have previously mentioned that I love NextDNS, but they do not make certain fundamental things, like managing your block and allow lists, very easy. Quite often I'll hit a URL that's blocked that I'd like to see - rather than use their app, I have to load their (completely desktop-oriented) website, navigate to the right tab, then add the URL in.

That's annoying.

Without writing an iOS app all of its own (which sounds like a lot of work), I wanted an easy to way to push URLs to the block or deny list. So I wrote an iOS Shortcut that works with a PHP script to send the appropriate messages.

You can find the shortcut here.

The PHP script can found at this Gist. You'll need to set the token, API key (API key can be found at the bottom of your NextDNS profile) and profile ID variables in the script. The token is what you'll use to secure your requests from your phone to the server.

I thought about offering a generic PHP server that required you to set everything in Shortcuts, but that's inherently insecure for everyone using it, so I decided against it. I think it would be possible to do this all in Shortcuts, but Shortcuts drives me nuts for anything remotely complex, and this does what I need it to.

It really is annoying how hard it is to manage a basic function. NextDNS also doesn't seem to have set their CORS headers properly for OPTIONS requests, which are required for browser-based interactions because of how they dictated the API token has to be sent.

OpenAI announced Sora, a new model for text-to-video, and it's ... fine? I guess? I mean, I know why they announced it - it's legitimately really cool you can type something in and a video vaguely approximating your description in really high resolution shows up.

I just don't think it's really all that useful in real-world contexts.

Don't get me wrong, I appreciate their candor in the "whoopsies" segments, but even in the show-off pieces some of the video is weird to just downright bad.

A screenshot of a video of a woman walking, where her thumb is approximately as long as all her other fingersHands are hard! I get it! But there's also quite literally a "bag lady" (a woman who appears to be carrying at least two gigantic purses), and (especially when the camera moves) the main character floats along the ground without actually walking pretty often.

Are these nitpicky things people aren't going to notice on first glance? Maybe. But remember the outrage around Ugly Sonic? People notice small (or large) discrepancies in their popular entertainment, and the brand suffers for it. To say nothing of advertisers! Imagine trying to market your brand-new (well, "new" in car definitions) car without an accurate model of said car in the ad. Or maybe you really want to buy the latest Danover.

An AI-generated commercial of a generic SUV with the word It seems like all the current AI output has a limit of "close-ish" for things, from self-driving to video to photos to even text generation. It all requires human editing, often significant for any work of reasonable size, to pull it out of the uncanny valley.

"But look how far they've gotten in such little time!" they cry. "Just wait!"

But nobody's managed to push past that last 10% in any domain. It always requires a human touch to get it "right."

Like the fake Land Rover commercial is interesting, except imagine the difficulty of getting it to match your new product (look and name) exactly. You're almost going to have to CGI it in after, at least parts, at which point you've lost much of the benefit.

Unfortunately, "close enough" is good enough for a lot of people who are lazy, cheap or don't care about quality. The software example I'd give is there probably aren't a lot of companies who'd be willing to pay for software consultant services who are just going to use AI instead, but plenty of those people who message you on LinkedIn willing to pay you $200 for a Facebook clone absolutely are going to pay Copilot $20 a month instead.

And yes, there will be those people (especially levels removed from the actual work) who will think they can replace their employees with chatbots, and it might even work for a little bit. But poorly designed systems always have failure points, and once you hit it you're going to wind up having to scrap the whole thing. A building with a bad foundation can't be fixed through patching.

I have a feeling it's the same in other industries. I do think workers will feel the hit, especially on lower-budget products already or where people see an opportunity to cut corners. I also think our standards as a society will be relaxed a little bit in a lot of areas, simply because the mean will regress

But in good news, I think this'll shake out in a few years where people realize AI isn't replacing everything any more than Web3 did, but AI will have more utility as a tool in the toolkit of professionals. It's just gonna take a bit to get there.

The funny thing is a lot of the uncanny stuff makes it look like the model was trained on CGI videos, which might be a corollary to the prophesied problem of AI training on AI outputs. The dalmatian looks and moves CGI af, and the train looks like a bad photoshop insert where they had a video of a train on flat ground and matted over the background with a picture.

Aw, man

Link Aw, man

Found out today Pogo sucks as a human.

I hate when I found out art I enjoy was created by assholes. But I have little problem dropping it from my life - there's way too much art out there made by people who aren't awful.

There goes half my glitch-hop playlist

Link Sacre bleu!

Gird your curds! Say a prayer for Camembert! A collapse in microbe diversity puts these French cheeses at risk.

An interesting unexpected side effect of uniformity in food (which I generally like!).

How many Ryan Reynoldses do we as a moviegoing public need? I felt like the original had it more than covered, but with the Chrises three (Pratt, Hemsworth and Evans) and now Ryan Gosling, I feel like my cup overfloweth with meta-acting and fourth-wall-chewing.

To be fair, Pratt did it more but RR did it most.

More stuff!

I think this is a symptom of wealth and age more than anything. Though it’s funny how 2/3 of those things are no longer even available for purchase

I think this is a symptom of wealth and age more than anything. Though it’s funny how 2/3 of those things are no longer even available for purchase

As part of my plan to spend more time bikeshedding building out my web presence than actually creating content, I wanted to build an iOS app that allowed me to share short snippets of text or photos to my blog. I've also always wanted to understand Swift generally and building an iOS app specifically, so it seemed like a nice little rabbit hole.

With the help of Swift UI Apprentice, getting a basic app that posted a content, headline and tags to my API wasn't super difficult (at least, it works in the simulator. I'm not putting it on my phone until it's more useful). I figured adding a share extension would be just as simple, with the real difficulty coming when it came time to posting the image to the server.

Boy was I wrong.

Apple's documentation on Share Extensions (as I think they're called? But honestly it's hard to tell) is laughably bad, almost entirely referring to sharing things out from your app, and even the correct shitty docs haven't been updated in it looks like 4+ years.

There are some useful posts out there, but most/all of them assume you're using UIKit. Since I don't trust Apple not to deprecate a framework they've clearly been dying to phase out for years, I wanted to stick to SwiftUI as much as I could. Plus, I don't reallllly want to learn two paradigms to do the same thing. I have enough different references to keep in my head switching between languages.

Thank god for Oluwadamisi Pikuda, writing on Medium. His post is an excellent place to get a good grasp on the subject, and I highly suggest visiting it if you're stuck. However, since Medium is a semi-paywalled content garden, I'm going to provide a cleanroom implementation here in case you cannot access it.

It's important to note that the extension you're creating is, from a storage and code perspective, a separate app. To the point that technically I think you could just publish a Share Extension, though I doubt Apple would allow it. That means if you want to share storage between your extension and your primary app, you'll need to create an App Group to share containers. If you want to share code, you'll need to create an embedded framework.

But once you have all that set up, you need to actually write the extension. Note that for this example we're only going to be dealing with text shared from another app, with a UI so you can modify it. You'll see where you can make modifications to work with other types.

You start by creating a new target (File -> New -> Target, then in the modal "Share Extension").

A screenshot of the XCode menu selecting The Xcode new target modal popover, with Once you fill out the info, this will create a new directory with a UIKit Storyboard file (MainInterface), ViewController and plist. We're not gonna use hardly any of this. Delete the Storyboard file. Then change your ViewController to use the UIViewController class. This is where we'll define what the user sees when content is shared. The plist is where we define what can be passed to our share extension.

There are only two functions we're concerned about in the ViewController — viewDidLoad() and close(). Close is going to be what closes the extension while viewDidLoad, which inits our code when the view is loaded into memory.

For close(), we just find the extensionContext and complete the request, which removes the view from memory.

viewDidLoad(), however, has to do more work. We call the super class function first, then we need to make sure we have access to the items that are been shared to us.

`import SwiftUI

class ShareViewController: UIViewController {

override func viewDidLoad() { super.viewDidLoad()

// Ensure access to extensionItem and itemProvider guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem, let itemProvider = extensionItem.attachments?.first else { self.close() return } }

func close() { self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil) } }</pre><p>Since again we're only working with text in this case, we need to verify the items are the correct type (in this case, UTType.plaintext`).

`import UniformTypeIdentifiers import SwiftUI

class ShareViewController: UIViewController { override func viewDidLoad() { ...

let textDataType = UTType.plainText.identifier if itemProvider.hasItemConformingToTypeIdentifier(textDataType) { // Load the item from itemProvider itemProvider.loadItem(forTypeIdentifier: textDataType , options: nil) { (providedText, error) in if error != nil { self.close() return } if let text = providedText as? String { // this is where we load our view } else { self.close() return } } }</pre><p>Next, let's define our view! Create a new file, ShareViewExtension.swift. We are just editing text in here, so it's pretty darn simple. We just need to make sure we add a close()function that callsNotificationCenter` so we can close our extension from the controller.

`import SwiftUI

struct ShareExtensionView: View { @State private var text: String

init(text: String) { self.text = text }

var body: some View { NavigationStack{ VStack(spacing: 20){ Text("Text") TextField("Text", text: $text, axis: .vertical) .lineLimit(3...6) .textFieldStyle(.roundedBorder)

Button { // TODO: Something with the text self.close() } label: { Text("Post") .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent)

Spacer() } .padding() .navigationTitle("Share Extension") .toolbar { Button("Cancel") { self.close() } } } }

// so we can close the whole extension func close() { NotificationCenter.default.post(name: NSNotification.Name("close"), object: nil) } }`

Back in our view controller, we import our SwiftUI view.

`import UniformTypeIdentifiers import SwiftUI

class ShareViewController: UIViewController { override func viewDidLoad() { ... if let text = providedText as? String { DispatchQueue.main.async { // host the SwiftUI view let contentView = UIHostingController(rootView: ShareExtensionView(text: text)) self.addChild(contentView) self.view.addSubview(contentView.view)

// set up constraints contentView.view.translatesAutoresizingMaskIntoConstraints = false contentView.view.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true contentView.view.bottomAnchor.constraint (equalTo: self.view.bottomAnchor).isActive = true contentView.view.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true contentView.view.rightAnchor.constraint (equalTo: self.view.rightAnchor).isActive = true } } else { self.close() return } } }`

In that same function, we'll also add an observer to listen for that close event, and call our close function.

NotificationCenter.default.addObserver(forName: NSNotification.Name("close"), object: nil, queue: nil) { _ in DispatchQueue.main.async { self.close() } }

The last thing you need to do is register that your extension can handle Text. In your info.plist, you'll want to add an NSExtensionAttributes dictionary with an NSExtensionActivtionSupportsText boolean set to true.

A screenshot of a plist file having accomplished the instructions in the post.You should be able to use this code as a foundation to accept different inputs and do different things. It's a jumping-off point! Hope it helps.

I later expanded the app's remit to include cross-posting to BlueSky and Mastodon, which is a double-bonus because BlueSky STILL doesn't support sharing an image from another application (possibly because they couldn't find the Medium post???)

Pair programming

Better than any rubber duck I've ever met.

Better than any rubber duck I've ever met.

Not wanting to deal with security/passwords and allowing third-party logins has given way to complacency, or outright laziness. Here are some troubling patterns I've noticed trying to de-google my primary domain.

  1. Google does not really keep track of where your account has been used. Yes, there's an entry in security, but the titles are entirely self-reported and are often useless (wtf is Atlas API production?). They also allow for things like "auth0" to be set as the responsible entity, so I have no idea what these accounts are even for.

  2. This would not be a problem if systems were responsible with the user identity and used your Google account as signifier. However, many apps (thus far, Cloudinary and Figma are my biggest headaches) treat the Google account as the owner of the account, meaning if I lose access to that Google account (like now, when I'm migrating the email off of Google), I"m SOL.

The RESPONSIBLE way to do this is allow me to disconnect the Google sign on and require a password reset. This is just lazy.

The best solution I've found is add a new account with an alt email address to the "team" account with admin ownership, but this is a hacky kludge, not a solution.