There are lots of ways to delight your users. Do the right thing. Do what they expect you to do. Figure out what they want even when they don't know or spell it wrong. You're probably familiar with the way search engines will take your search string, fix a misspelling, then run the search and make sure that's what you mean. And they do that for more than simple transpositions like teh -> the or nad -> and.
Other places you might see it is in git. If you've ever tried to run git status and typed git stats instead you've seen it tell you that you meant status. Arc goes one step further. If you run arc diff --no-lint arc will respond with (Assuming '--no-lint' is the British spelling of '--nolint'.) and then do what you meant. Git can do the same thing, but by default it just tells you the options. Facebook gives you extra passwords
If you've ever wondered how they do that, one common way is to check for valid words, then, for invalid words use something called the levenshtein distance to find the best alternative. Once you have alternatives you can present them to your user and maybe ask them if they want to try one. We do the first part in the `infra` cli.
Or, you can go the sl route (brew install sl on OSX or sudo apt-get install sl on linux) and just have something that handles a common typo. And don't suggest lots of aliases. That's just no fun
We all work on distributed systems. We all like superpowers. If you were Distributed Systems Man what superpowers would you want? As a user of distributed systems I'd want, in no particular order:
*Idempotency*: I want to be able to send the same message multiple times until I get a response and not worry about partial application. If I have to set something and get a connection error then have to check to see if it was set, figure out the new set command, and try again I will get it wrong. Don't make me do that.
*Availability*: I want the system always work. Period. I should never get a "System unavailable" message. And if I do retrying immediately should just work.
*Scalability*: I want the system scale (up/out) when it gets slow. Make it automatic. Make it so I don't have to care if you scaled up/out. I want to send my message and have it get to the right place magically.
*Durability*: I want the system to remember what I told it. I don't care if the power fails in the DC or someone with a backhoe digs up a fiber. If I get a message that my submission (whatever it was) worked then the system should never forget it
*Consistency*: I want to be able to set something and then I (and everyone else) immediately be able to get that value. I don't want to care where the other person is, but when I get told I've updated something it should be updated for everyone.
Of course, providing those things is hard. According to the CAP theorem it's not possible. But we can come close. Caches. Queues. Shards. Duplication. And SLAs. Don't forget them. Maybe you can't promise everything, but figure out what you can promise, then deliver on that. So what's your superpower? Share in the thread.
Speaking of APIs, how do you know the API (or UI) you've lovingly crafted solves a user's problem? I assume
you've done your homework and talked to your customers, observed what they're doing, and understand their domain
reasonably well, and what you've built works, but that's not enough. Remember, customers (users) want solutions
(to their problems), not features and cool technology. So how do you know if you're actually solving their
problems?
Ask them, of course. Whether you call it a prototype, a beta test, or "Hey Pat, can you come over here and try this out
for me?", getting real feedback is important. Is it as obvious as you think? Do they click on the
right button or go searching for one with a different name? Does the workflow make sense? Do they get
stuck? Watch them and see. Do they look confused or delighted? Ask people with different experiences. They'll
see things differently and have different responses.
You're also checking out any documentation/examples you have. For an API, can your target user modify something
and make it their own? Do the names tell them what to expect? Do they use the API the way you expected?
And instrument your system. Your users will often tell you what they don't like, but objectively, how often do
they do the wrong thing, click the wrong button, or call an API with incorrect parameters? That you can track
yourself.
REST is representational state transfer, and is in truth transport agnostic, but when people talk about RESTful
APIs they're usually talking about REST over HTTP. And when you're
talking about HTTP and REST, you need to think about your `goesintos` and `goesoutofs`, your verbs and your
bodies. HTTP 1.1 only defines 8
verbs, and which one to use for a given method is pretty well defined. When you're defining your API think about safety and idempotence.
Distributed systems live and die by idempotence, and we have a lot of distribution.
Think about your inputs. Are they all required? Are there reasonable defaults? Is there some set of values that
are mutually exclusive? That might be OK, but be mindful of it. And don't accept inputs if there shouldn't be
any. It's not an error to send a body with a GET request instead of using query parameters, but GET requests are
supposed to ignore the body, so from the principle of least astonishment, it's a bad idea. You never know who's
going to drop/ignore it along the way.
Think about your outputs. First of all, the HTTP spec has defined as set of return codes. Use them. And use
them as they're intended. Not all successes are 200, but all 2XXs are successes. If you need to provide more
details then use the body.
And of course, this doesn't apply to only REST over HTTP.
Idempotence and not astonishing your users is important regardless of what your API is. If you're using gRPC
it's not as clear what is idempotent, but minimize side-effects. Setters clearly change something, and Getters
never should.
I've talked about mission and vision statements, but I haven't talked about charters. You'll hear charters talked about in two contexts, team and project. Personally, I don't believe in team (or org in general) charters. Those are mission and vision statements.
To me, a charter has an end goal (just like a vision), but when the end goal is reached the project is over and work stops. Yes, a project can evolve and where you end up isn't always where you thought you were going, but there is an expected end. A charter is both more and less than a vision and mission.
It's less than that in that its scope is in some ways smaller. Not only does it have an end, projects and their charters exist within a larger organization. Self-driving cars that drive more than themselves is a vision. PLT is a project and has a charter.
A charter is also more. Besides having an end state (vision) projects also have roles and responsibilities defined. Who's involved? Who's responsible for what? Who are the pigs and chickens? What are the processes the project will use? Some of these need to be written down, others are assumed/inherited from the org.
Regardless of whether all of those things (and others) are explicitly written down or not, you've got them. The benefit of having them written down and agreed to is that whenever there's a question you can refer back to the doc and get clarification.
In the spirit of YAGNI, sometimes it’s better to put off a decision until a later date. Management books tell you a sub-optimal decision now is better than no decision. And that’s often the case, especially when there are lots of people sitting around doing nothing while they wait for a decision. Sometimes you do need to choose, and in those cases making a choice is better than not.
Sometimes, the reason you need to make a choice now is because of time constraints. For whatever good and valid reason the choice needs to be made, so you make it. You do the best you can with the information you have at the time, then you make the best of it and work from there.
Other times, particularly at the beginning of something, you have lots of time and freedom to make decisions. In those situations it can often be better to not decide. Take the time to investigate. Look at your options. Look at the state of the art. Think about use cases. Build a framework, buy a framework, or just do it? If you’ve only got one use case you don’t know. If you only have two then a simple if statement is enough. At 3+ you can really decide if you need to build a generic system.
Similarly, if you decide on an architecture before you have all the requirements and the requirements change you now have to either adjust your architecture (expensive), reduce features (unhappy customers), or do unnatural things with your code (tech debt).
So, when it comes time to make your next decision, think about it, and YAGNI right now
Domain Specific Languages are a thing. They let you express
what you want in terms relevant to the problem at hand, and can hide a lot of complexity. Consider the lowly
SQL select statement. You get to specify a few parameters and the
system does yeoman’s work to actually figure out how to optimally read the data. CSS is also a DSL. Our team
wrote and used Turtl to process JSON files and it makes templating and data extraction a lot easier. Those are all
text based DSLs, and we’re all very familiar with them, but not all DSLs are text based.
Most form builders and interface layout tools are visual DSLs. Yes, there is a config file behind the UI that
you can muck with, but the intent is to use the visual interface. There are even visual DSLs for personal robots.
The world’s largest tire manufacturer (LEGO) also produces a visual DSL for it’s Mindstorm
robots. It’s based on LabView, which is another visual DSL that’s widely used for industrial
purposes.
But that doesn’t mean that DSLs are the
solution to all your problems. For one thing, they’re specific, which means if your
problem isn’t covered by the designed use cases you’ll have a very hard time doing what you want, if
it’s even possible. It’s also another level of indirection which makes it harder for the end user to
know exactly what’s going to happen. And like other languages, how you handle the edge cases is important. And
you need to be consistent across versions. You never really know how your customers are going to
use it, but you need to support/maintain those use cases going forward.
So, a DSL may be the solution to your problem, but make sure you include all the future costs in your evaluation
How’s that for an attention grabber? If we’re not here to write code (or design hardware, or manage
projects/products, or whatever it is you spend your time doing), then why are we where.
We’re here to increase business
value by making cars that drive more than themselves. Very often that means writing code, or designing
or
managing, but those are the means to an end, not the end in itself.
I’m a software engineer, so I’m going to phrase this in software engineering terms, but it applies
to all roles. Sometimes the best code you write is the code you don’t write. Yes, we all like to start
from
scratch and build bespoke systems, and sometimes that’s the right answer, but before we go and do that, we
really need to think about why
we’re doing it.
New systems are fun and easy to build. They don’t have (as many) constraints. They can be exactly what we
want, and require nothing else to change, but as pointed out last week, another layer of indirection is just another
place
for
problems. Every system has its functionality, but it brings with it complexity and unexpected interactions. It
brings with it opportunity costs when you’re building it and maintenance costs. It might very
well
be the right answer is to build something new, but take the time and be sure before you get started.
Last week I talked about some principles for
developers. Today I want to drill down into
the Code For the Maintainer idea. As developers we
really like
to work on green-field projects. There’s room to explore and innovate, and you don’t need to worry
about
pesky things like backward compatibility and existing user expectations. However, most of the work we do
isn’t
green-field. We work inside existing systems, adding functionality and fixing bugs. We extend them into new
operational domains. In those cases we act as a maintainer of code, rewriting/refactoring existing code.
That’s where this principle becomes really important, because the first thing you need to do, before you
even
think about doing what you came for, is to understand not only what the code actually does, but what the author’s intent
was.
So one of your jobs, as the writer of code, is to make the job of the maintainer easier. Make sure things are
named
well (they do what they’re called and nothing else). Make sure the flow is broken down into manageable
chunks.
Avoid side-effects. Even more than documenting the “What”, make sure to document the
“Why”
and the constraints/expectations.
And please, please, please, document how to setup/install and use any additional
tools/permissions/preconditions necessary to build and work with the code. Even better, write a script and put
it in
the README.md. You do have one of those, don’t you?
You never know who’s going to look at the code 6 months from now. How many times have you looked at some
code and asked what noob wrote it, only to run git blame and find
out
it was you? Do everyone a favor and write for the maintainer. I, and future you, thank you for it. As Ward
Cunningham said:
“Always code as if the person who ends up maintaining your code is a violent psychopath who knows where
you live.”And his follow-up
I usually maintain my own code, so the “as if” is true.
We’ve all looked online for solutions to problems, and that’s a good thing. Learning from others
gives you time to focus on what’s important for you and your team, adding value for the company instead of
reinventing the wheel. Whether it’s personal blogs, public Stack Overflow, internal Stack Overflow, or a
generic Google search, it’s easy to find code snippets that purport to solving your problem, or at least
something not entirely unlike your problem. Once you’ve found it, the question then is, what do you do
with it.
It’s tempting to just copy/paste it into your code and move on, but that’s probably not the best
choice. If nothing else, there’s no guarantee that the code you’re looking at actually works the way
you want it to. Just because it’s got lots of up-votes, it could still be wrong. Or, the constraints on
the code don’t quite match the ones you’re dealing with. Or there might be some edge case you or the
author haven’t thought of. Or it might have some kind of license on it. You definitely want to check up on
that.
Bottom line, internet searches can be really helpful, but they’re not without problems/risks.
Here’s the story of one of the
most copied pieces of code from StackOverflow, and how it’s wrong.