Don’t Avoid Difficult Things

Once, when looking for a programming job, I applied for a job I didn’t get. That’s not really surprising – I was just starting out, and there are lots of rejections on the way to any success. But the mistake that eliminated me from the applicant pool is a special case of a general human bias, so I want to write about it, in the hopes that I’ll save some of you from making a similar mistake: I accidentally avoided a difficult thing, even though I knew it was there and knew it was a difficult thing.

I think humans carry a strong temptation to avoid hard things, or at least to avoid certain things. A friend once remarked during college, “You can tell I should be studying for exams, because my room is very clean, and I balanced my checkbook, and I cleaned out my closet.” Her point was, “You can tell that I’m avoiding a difficult task, because I am being unusually diligent in easy, low-priority tasks.”

Joel Spolsky made a similar point about buying colored folders when he should have been doing something important. Paul Graham describes a related phenomenon he calls schlep blindness, by which he means the difficulty humans have in seeing difficult tasks as opportunities, or even seeing difficult tasks at all.

Two books on the subject that I highly recommend, are Angela Duckworth’s Grit and Matthew Syed’s Black Box Thinking. These books both touch on why some organizations and people benefit from mistakes, that is, they learn, grow, and keep going, and some do not. I think that, in large part, it boils down to a willingness to confront head-on whatever the problem is.

So back to that time I didn’t get a job:

After I had cleared the phone interview, I was sent instructions for a programming test. I thought it was pretty straight forward: Basically, build a small web application that can call the company’s API to validate log ins and update contact information. I had a core site up and running pretty quickly, spent a while on UI polishing, playing with CSS and JavaScript to make the experience a little snappier, and confirming that I could change a contact’s name and phone number over the API.

I was pretty excited, because the features I tested worked. Hurray, I thought, I’m calling this API like a real programmer. Look at these little success messages when the database update succeeds, look at these crisp validation messages when the form is in a bad state and can’t be submitted.

In the back of my mind, I knew that updating a user’s actual login credentials via my application was potentially problematic, because it would make the local data about the log in be out of date with the API I was calling, and for some reason I just didn’t test that part thoroughly. I kept retesting parts of my app that I knew would work, and then avoiding the part of my app that I was afraid was broken.

As you’ve probably guessed, when I submitted my little web app for grading, the company I was applying to immediately found a bug that occurs when a user changes their username, and then tries to log in again with the new username. Somehow, I had managed to pat myself on the back about my application while simultaneously not testing a likely point of failure. I’m happy with the job I ended up at, actually very happy, but I think there’s an important lesson here:

Even though I thought I was doing my best, my testing was self-congratulatory rather than dispassionate. I should have been trying to get my app to malfunction, not trying to remind myself that the code I wrote did what I wanted. I was, in some way, voluntarily blind to a point of failure. It’s a mistake I hope not to make again. I think one countermeasure against this sort of voluntary blindness is to ask yourself, not “Is my app tested?” but “If I were trying to crash this app, what would I do? Invalid inputs? Too many requests? Telling it to open a file and then deleting the file while it was still working?” In short, I recommend that you do terrible things to your code.

I missed out on an offer because I was focused on what could go right with my web app, and I should have been focused on what could go wrong.

Anyway, I hope this post will keep you from repeating my mistakes.

Till next time, happy learning!

 

-Will

The Developer who Knew Too Much

I have a pretty cool calendar in my cubicle at work. Every month it shows me a different software development anti-pattern, and has a clever little picture to help me remember them. I’ve already ordered next year’s.

Earlier this year, the calendar had a picture of a doctor writing “Diagnosis = Sick” on a clipboard, and an admonition to write descriptive error messages. I started wondering, “Why is it hard to write descriptive error messages?” One explanation is that the curse of knowledge. The curse of knowledge basically says that if I’m trying to guess how other people behave, I will accidentally bias the guess with my knowledge. That is, I can imagine how I would act in different circumstances, but I can’t how I would act if I knew different things.

Writing error messages, of course, runs square into the curse of knowledge: When I write an error that my program will display, I, by definition, know what is wrong, because I know the state the program must be in display that error. Because I know what’s wrong, I am of course able to interpret more or less any sentence I compose as an error message to mean what I think it will mean. It is very difficult for me to guess how someone else will interpret that error message. They might find it perfectly lucid, or it might be immensely baffling; I can’t easily imagine seeing the error message while not knowing what it means.

On the other hand, I think some exceptions are inherently more explanatory than others: Let’s take C#’s IndexOutOfRange exception. You had an array of 5 things, you asked for the 6th thing, boom! IndexOutOfRange. It’s super easy to interpret because it can only mean one thing. Other error messages that seem super easy to interpret are, in no particular order:

  1. ArgumentNullException: This method needs an object, you gave it a null pointer instead. Shame on you.
  2. KeyNotFoundException: There was a collection of keys and values. You asked for the value associated with some key that’s not present in the collection.
  3. Perhaps clearest of all: DivideByZeroException. Turns out C# can’t do that because, well, math can’t do that.

What do these exceptions have in common? Well, for one thing, they’re all boneheaded exceptions. What makes them boneheaded exceptions? They’re the programmer’s fault. If you’re dividing two numbers, you could always check to see whether the divisor is zero before preceding. If you’re accessing an array, you really ought to know what size it is before you ask for the nth element, etc. Maybe we can call them boneheaded because you’re violating an assumption that the program has to make. If you’re asking for the nth element in the array, obviously the underlying code has to assume that the array contains at least n elements. If you’re dividing numbers, obviously the program has to assume that the divisor isn’t zero.

Some exceptions inform the consumer of the assumption they’ve violated. The ArgumentNullException is a good example. Maybe I thought I was allowed to pass in a null value to that method, or, more likely, maybe I thought the argument that I’d passed in wasn’t null. The programmer who threw the argument null exception is telling me: “Look, when I wrote this method, I assumed that this argument was going to be a real object, OK?”

We seem to have two types of exceptions, then: Exceptions that are obvious because they contain thee assumption necessary for the command to be logical (e.g., divisors can’t be zero) or exceptions that are obvious because they tell you what assumption you’ve violated.

Exceptions get harder to diagnose as the assumption that’s been violated gets less clear. For example, a null reference exception just means “You assumed some object wasn’t null, because you accessed one of its members, but actually it was null.” This exception is harder to diagnose because it’s not declared in your code which objects you are assuming not to be null.

Perhaps now we can formulate a general rule for error messages: Error messages are helpful if they tell you what assumption you’ve violated. File not found? Obvious: violating the assumption that there was a file at that path. Argument null exception? Obvious: violating the assumption that the argument was not null.

The other day I lost some time to an invalid operation exception. It was a little tricky to diagnose, because it basically says: You thought you were allowed to call that method while the object was in the state it was in, but you were wrong. This is a bad error message because it doesn’t tell me the precondition I’d failed to meet. The error would have been much more helpful if it had said, “You need to call .Initialize() before you call .Execute(),” for example. In other words, it should have told me the difference between what the person who wrote the code that threw the exception thought would be true, not what I thought would be true. Telling me I’ve performed an invalid operation tells me nothing I didn’t know: You tried to call this method and the call failed.

So here’s my rule for writing error messages: Tell the consumer what assumptions about the state of the program you had to make in order for that command to succeed, and which ones weren’t true. Instead of saying, “Invalid input,” say “That field must contain an email address.” Saying “invalid input” tells me that my input is invalid, which I already knew just by the fact that there was an error. Telling me “that field must contain an email address” tells me that you, the author of the program, had to assume that I, the user of the program, would type an email address, but I didn’t. It gives me the information you have that I lack, so it helps me.

In other words, we can say this:

All error messages should report what assumption about the state of the program was violated to cause the error. They must always report assumptions made by the implementer and violated by the consumer, rather than assumptions made by the consumer, since telling the consumer what assumptions they’ve made doesn’t help them.

So, the next time you’re writing a part of a program that might throw an exception, ask yourself, “what assumptions did I make whose violation led to this error?”

Also, if you have a favorite example of an incomprehensible error message, please share it in the comments.

Till next week, happy learning!

 

-Will

Avoiding arrogance, again

The past few weeks I’ve noticed that I have a bias in my interaction with other people, especially other developers, that is probably not doing me favors or winning me friends: I really judge people when they misuse technical vocabulary.

Because it’s the 21st century, of course, when I notice a negative personality quirk in myself, my immediate reaction is to tell strangers on the Internet. And maybe we will learn something along the way.

Now, I’ve always had, and at least sort of known about, a mental heuristic in favor of well-used technical terms: If I’m talking to someone about stats, and they start to tell me something is “statistically significant,” but they really just mean “important and interesting,” I begin to doubt their knowledge of statistics. Fair enough. The reason I am suddenly blogging about this heuristic and calling it a bias is because it think it’s starting to have a high false-positive rate. And, of course, a heuristic for guessing whether other people know what they’re talking about that’s beginning to reject other people’s thoughts pretty frequently is just another name for turning arrogant in my middle age, so let I’d like to pick the experience apart and see if I can’t learn to stop it.

I noticed the other day when I was listening to .NET Rocks, a favorite programming podcast, and one of the guests didn’t think of the name of the adapter pattern in time. He stumbled around for a second, and then explained the pattern perfectly well and went on with his talk. And I actually caught myself thinking, “ha! I guess that guy should go review his technical vocabulary” (or something equally uncharitable) before I caught myself. This guest is a veteran, accomplished engineer being interviewed on one of the most famous programming podcasts in the world. And here I am thinking I know better just because he stumbled over a word. This is a bias that has to end.

So how do I go about counteracting this bias? I mean, constantly underestimating other people just because they stumble over words (or I think they stumble over words) is at best misinformation about my surroundings, and at worst downright mean. And giving myself the advice, “don’t have bias” is sort of like giving the advice “don’t drown” during swim lessons: surely correct but totally useless.

I think what I should do is cultivate the opposite habit. It’s not New Years, so here’s my early-autumn resolution: Whenever someone misuses a technical term, I’ll try to deliberately assume that (1) the person knows the correct term perfectly well and just slipped and (2) the rest of the person’s ideas and thoughts might be perfectly sound, even if I have to fudge the meaning of that one term to make the sentence compile.

I suspect that as I practice this habit, I’ll start getting along with people better, since everyone misspeaks and no one wants to be judged for it, however silently, and I’ll probably also start learning more, since I’ll reject fewer ideas for superficial reasons.

I almost hope no one reads this, but if you do, I hope it will help you not to repeat my mistakes. And if you’re someone who forgot the name for some technical thing recently, and noticed me judging, please accept this apology.

Till next week, happy learning!

-Will

Tech Books Second

I heard a joke the other day (on .NET Rocks). Something to the effect of “you know what make good door stops? Old JavaScript books.” The point of this joke is that the JavaScript language changes really fast, which is true, but the joke got me thinking. What are technical books good for at all?

People certainly write plenty of them, and publish plenty of them, and, free markets being what they are, I can only conclude that this means that people buy plenty of them. But what good are they? I mean, we can lookup syntax on StackOverflow and just muddle through other people’s code samples on the internet to learn programming, right?

Learn Fast By Alternation

I think that technical books are supremely helpful, but that you have to use them second. Here’s a good example: I did some self-teaching mostly in Java and Python, did an internship in C#, and now work mostly in C#. About 4 months into the C# job, I picked up a copy of C# in Depth by Jon Skeet. It was a wonderful experience. I had enough context to understand what was being written, and I found that I progressed quickly.

The same thing happened again with git. I started using git for side projects about a year ago, and had some fun and some confusion, which I gather is a typical experience for people starting out with git. I was figuring some things out, developing some side projects that were version controlled on Github, and then I started reading Pro Git. The same thing happened. I had enough experience and enough context that the book made sense quickly, and was prompty helpful. Therefore, I am going to propose a bit of general advice for using technical books:

Read technical books second, after you’ve muddled through a bit.

I think this rule really might just a be a special case of watching your input/output ratios. In other words, if you start by reading a technical book right away, your input/output ratios will be all out of whack; you’ll be way too input-rich to retain anything. But if you muddle through for a while, you’ll have a backlog of questions. Then when you read, some of your questions will be answered, so you’ll remember what you read. After you’ve read, go back to writing more code for a little while; you’ll have a backlog of things you read about but want to try. Your code will improve from trying new things, and you’ll retain the reading better because of the practice. Repeat.

Look Up Syntax Elsewhere

There are many good reasons to by a technical book, but a desk reference for the syntax of <insert language here> is not one of them. Online searching, StackOverflow, and StackOverflow’s new documentation section are much better suited for that.

Also, since we know we’re not using our technical books as reference books to look up documentation, we will make different decisions about which technical books to purchase at all. For example, if you see a book called A Pocket Reference to Haskell Syntax, you may want to keep looking; pocket references are slow, non-scaling Google. Instead, I recommend looking for books that you choose based on the quality of thought and writing. My favorites recently have been Jon Skeet’s book, mentioned above, and Steve Krug’s Don’t Make Me Think, Revisited. These books are quite different from one another, but they have one thing in common: You see useful human thought if you read them. Don’t buy a book so you can look up the order of arguments for some formatting function on page 634; get a book with large chunks of useful human thought in them instead.

I think choosing technical books that are well-written is also particularly important. I’ve always found that if I read the same author a lot, I’ll begin to sound like him, if only a little bit. Unconscious imitation is a major part of human language. Therefore, I really recommend only reading books that are a joy to read; forcing your head through garbage technical prose will only increase the odds that you’ll write garbage technical prose of your own one day, and the information you get will be harder to remember than if you’d read good books.

Fundamentally, I think human brains enjoy learning, and books that really take your imagination (and take your whole afternoon) are just more fun to read. In a sense, how fun a book is to read can be a decent measure of how quickly you’re learning from the book. (Of course, this can have false positives. Tom Clancy is pretty fun to read, but he’s not going to teach you Python very fast.)

So, do yourself a favor. If you’ve been muddling through a language for a little while, perhaps even having taken your first bite of the elephent, ask around and find a really solid, well-written, interesting book on the technology you’re trying to use, and pick up a copy. Once you’ve done enough muddling that your brain is thirsty for some input, I think you’ll find it very rewarding and enjoyable. And if you find a particularly good one, post it in the comments!

Till next week, happy learning,

-Will

 

Hard problem, or wrong problem?

 Is this problem hard, or am I trying to solve the wrong problem?

Before I had my first programming job, two friends and I wanted to build a start-up-style, I have an idea for an app lets buy a domain name, kind of business. Long story short, we had a good time, but didn’t really get anywhere. I’ll leave most of the details out, since they’re not pertinent to this story, but I’ll share one episode.

I had bought a book about building your own website (I think it was focused on LAMP stacks, and we were using IIS because one of us knew C#, but anyway), and this book had mentioned something about sign-in cookies and not storing cleartext passwords. It was good advice, but I completely missed the point. After I read this book, and we wanted to build a mobile app, I started trying to write, from scratch a system for letting people sign into websites. In .NET. Pretty recently.

No, that’s a hard problem. But it turns out, I was trying to solve the wrong one. The problem I was trying to solve wasn’t the general, “how should we make difficult to guess cookies” and “how should we store salted, hashed passwords.” It was actually just, “how do we tell the .NET framework, which was built by smart people who already know how to do these things, that we want a website where users sign in.” I was reinventing the wheel.

I have learned a little bit about building websites since then, but that instance of reinventing the wheel seems to me like an instance of a pattern I see a lot: Making your life hard by solving the wrong problem. But what do I mean by “the wrong problem?” I mean, if I’m solving the wrong problem sometimes, having a procedure for converting wrong problems into right problems would be pretty simple.

Eric Lippert wrote about this too, in a less direct way. (Note: I’m about to spoil the punchline of a 5 part blog series. If you want, read the series first and then come back. I’ll wait.) Anyway, in this series, Eric proposes a simple programming problem, a game where we have wizards, warriors, swords, and staves. Basically, a wizard and a warrior are both types of player, and a sword and a staff are both types of weapon, so we go off and start building our object hierarchy. Then Eric asks, quite reasonably, “how do we represent the rule that a wizard can only use a staff and a warrior can only use a sword”? Mr. Lippert then shows us a half dozen increasinbly perilous and clever attempts to make C# represent this rule, but because all the attempts are centered around the type system, they all amount to “A player wielding a the wrong weapon will throw an InvalidCastException, or something like it, at runtime. That’s not much help, because the whole point of having a compiler is that it keeps us from making certain runtime exceptions. So how does Eric propose we resolve this:

The fundamental problem is my initial assumption that the business rules of the system are to be expressed by writing code inside methods associated with classes in the business domain.

I wonder if his explanation for why the poor people implementing Wizards and Warriors are on the wrong track has anything in common with why my first attempt at building a web start up was on the wrong track.

I think that what the two situations have in common is that they both have someone trying to solve a problem while working under a mistaken assumption that actually makes the problem harder to solve. I was trying to solve the problem of letting people sign into my website, and working under the mistaken assumption that I had to write all the relevant code myself. When the poor (imaginary) team on Warriors and Wizards was trying to get their application to run, they were working under the mistaken assumption that all the rules had to be on classes that were named after physical objects in the game.

So, the next time you’re working on a problem that seems hard, or it seems like the tools don’t fit, ask yourself “what assumptions am I working under, and are they valid?”

Till next week, happy learning.

-Will

 

Tools, Fools, and Types

I once knew a skilled carpenter. He had a saying, about screw drivers and drills and such:

It’s not the tool, it’s the fool.

What he meant is that a master carpenter with poor quality tools is better than a novice carpenter with high quality tools. His particular example was to say “A master mechanic can take apart and repair the entire engine room on a ship with a flathead screwdriver and an adjustable crescent wrench. If you’re blaming your tools, you should be blaming the fool who uses them instead.”

I have been thinking a lot lately about programming languages because there are so dang many of them, and I can’t possibly learn them all. I’ve been to meetups recently focused on Rust, focused on F#, focused on JavaScript. My career is a time-bound operation. I have finite waking hours. (Finite keystrokes). So I want to learn primarily the best languages, the best tools, but I am not sure what they are. Not that I think there will be a silver bullet, a programming language that makes everything better, but that I want to be a little bit less foolish about what code I write.

The other day I was at a Meetup for the Rust programming language. It was by the founders of a Rust-focused tech consulting start up. I really enjoyed it and I learned a lot, but it got me thinking about what Uncle Bob has called the Type Wars.

Briefly stated, the type wars are an argument between people who think that a strict language (in the informal sense of the word, just a language with rigid rules of some kind) will help you be more productive than a loose language because there are certain types of bad programs it won’t let you write. On the other side, people argue that a loose language will let you write faster, and then you just write tests to prove your program is correct, so all the rules in the strict language will just slow you down. We could list F#, Haskell, and Rust as being firmly in the strict camp, while we could list Ruby and JavaScript on the loose side.

Mark Seemann wrote a fantastic example of the strict language argument. He essentially argues that, out of the set of all programs, most are very bad and obviously wrong. Therefore, languages that have strict rules produce better programs because fewer of the really bad programs are allowed.

On the other side, we have The Shocking Secret About Static Types by Eric Elliot. The article talks about the recent popularity of TypeScript, a language that lets you annotate JavaScript with more type information (that is, it lets you write JavaSript, a very loose language, in a way that is more rigid.) The article basically argues that, although using compile-time types does make it harder to make certain errors, it doesn’t reduce bug count, and that what we should really do to reduce bug count is having better unit tests. Elliot’s main source seems to be a paper in which the authors looked at the top 17 programming languages on GitHub, and looked at the 50 most popular projects in each language, to try to discover whether popular, well-maintained projects in one language are measurably better than those in other languages. That article is an interesting read, but I am always dubious articles that rely entirely on statistics about some set of human activity, simply because the assumptions that went into gathering the statistics might be flawed, and the assumptions that went into interpreting the statistics might be flawed, and the errors in these assumptions multiply one another, so the article could be way off. Perhaps I’ll write more on that later.

Anyway, this got me thinking:

Can I make an analogy between writing programs and searching for something in a finite space? In other words, if we do a thought experiment where the programmer is searching for the right program in the set of all programs that his language will allow, can we gain any useful insight into programming language design? I like this approach because speeding up a search algorithm is much easier to reason about than the entire process of creating software is, and the analogy is not that terrible; programmers often consider and reject many alternatives before they are happy with a piece of code, and that is a little bit like a search algorithm.

Mark Seemann and others want to optimize the search for good programs by shrinking the search space. In other words, they argue that if there are fewer valid but flawed programs, the search for valid, non-flawed programs will go faster. Based on our analogy, they’re right. If you’re looking for a specific book somewhere in the library, good luck. But if you know anything about the book, the author or the call number the library gave it, you might be able to find it quickly. Shrinking the legal search space is a smart optimization for search algorithms.

What about JavaScript and Ruby? You can do all kinds of things in JavaScript that will deterministically fail at runtime, and nothing will stop you. No compiler will say, “Wait, that doesn’t make any sense! Error!”

So for JavaScript, the search space is huge. JavaScript doesn’t have any of the rules that stricter languages have that would prevent you from writing obviously senseless programs. So do the rigid languages win this round?

I’m not sure. In the rigid languages, the search space is smaller, but the search is slower. In JavaScript, the time from having an idea to having a testable little chunk of code might be briefer. So although the space the programmer has to search through is larger, although there are more possible programs to consider, the developer won’t have to consider each of them for quite as long.

If we really push this analogy, the question becomes this: Does the shrinking of the search space help more than the adding of picky rules that slow one down hurts? I don’t know the answer to that question, but it is definitely worth thinking about.

My personal preference is usually for the rigid language. Eric Lippert says that one benefit of these rigid languages (my term) is that the language

“… is a tool for producing a partial proof that the program is correct, or at least not broken in some obvious, detectable way.”

I find this partial proof comforting. It makes the number of things I have to worry about smaller.

I personally think, also, that a specific subset of the rigid languages, namely the functional languages, have a distinct advantage here: They are better at dealing with concurrency.

Basically, concurrency is the set of programming problems that arise when more than one process is acting at the same time. Because functions’ outputs depend only on their inputs, and not on the outside state of the world, they are less risky to use in situations where many processes are acting than other constructs are. I think we might reach the point where functional programs, which are much less reliant on storing the state of some process than other programs, are just better for dealing with the kinds of problems we have.

Still, I think the size and space of the search space depends more on the problem domain than on anything else. Perhaps the type wars end in stalemate, and it’s really about the fools writing the code.

Till next week, happy learning!

-Will

Watch for Slow Trains

The other day I was listening to .NET Rocks episode 1327 and I heard an interesting quote. One of the guests, Jamie Dixon said:

It’s a slow moving train, but if you sort of stand there with your OO [object oriented] imperative code that train does … leave you behind every day

Now, this quote has been keeping me up at night a bit. Not because I’m afraid that I’ll be stuck writing object oriented, imperative code forever – I firmly believe that functional programming is the future – but because I don’t know which slow trains I’m on, and which slow trains I’m not on.

Health is a good example of a slow train. I know that I’ve gained a little weight since I started computer programming. Pants that were comfortable last summer still fit, but they’re not comfortable. It’s a slow train, but it is leaving me behind. So I decided to re-evaluate a little bit. I’m training for a 5k. That’s not exactly a marathon, but it is a start. It’s a slow train in a direction I want to go.

This blog is another example of a slow train in a direction I want to go. I check the stats every morning, in case a post went viral the night before, and it didn’t. I usually have only a few reads, and probably from people who know me personally, but it doesn’t matter. I’m building up a long history of posts, getting my name out, practicing my writing, and learning. I hope I’m even helping a few other people learn. Again, slow train headed in the right direction.

That slow train quote is a great example of the right little thing. It started me thinking. It was someone else’s aside, but turned into a major catalyst in my mind.

I think that’s it this week. I do have one announcement: I’ll be blogging every Monday and Wednesday morning, instead of just every Wednesday. (This blog train is moving a little slow for my taste.)

I also have a question for you:

What slow trains are you on, and do you like where they’re going?

Till next week, happy learning,

-Will