“I’ve never had a problem with it.”

From time to time, when I’m discussing possible tools or techniques for a project with other developers, one of them will defend their choice by saying that they’ve never had a problem with it.

This immediately makes me sceptical. If you say you’ve never had a problem with something technical, it tells me one of two things. Either you’re lying, or you don’t have enough experience with it to be able to recommend it.

Pretty much every tool, every technique, every framework, every language, every best practice, has its pitfalls. It may work well in some situations but not in others. It may only work if you follow a strict set of protocols to the letter. It may have bugs or unintended consequences. It may not scale to meet your needs. It may deliver benefits that you don’t need at the expense of ones that you do. It may not even deliver the benefits it claims to deliver at all. In nearly every case there are multiple ways of getting it badly wrong. More often than not, you don’t discover what the pitfalls are until you’ve been using it for quite some time.

There’s no shame in admitting this. Problems with your approach aren’t necessarily a deal breaker. But if you’re recommending something to me, I want to know that you’ll be able to steer me through the problems and teach me how to avoid them, mitigate them, or recover from them. By saying that you’ve never had a problem with it, you’re telling me that you will not be able to do so. And that is a deal breaker.

“That’s just your opinion” means “I’m not listening”

Ever been trying to present a reasoned argument, with evidence, for something, only to be rebuffed with a response like this?

  • “That’s just your opinion.”
  • “Well, everyone’s entitled to their own opinion.”
  • “You just don’t like it because it’s not cool and trendy.”

What they mean is this:

  • “I’m not listening. I’ve got my fingers in my ears. Neener, neener, neener.”


Moving a problem from one part of your codebase to another does not eliminate it

This is, of course, a statement of the obvious, but I’ve come across quite a few “best practices” in recent years that violate it.

People come up with some design pattern or other, telling you that it solves some problem or other. At first sight, it appears that it does eliminate the problem from one part of your codebase, but on closer inspection it turns out that it merely shifts it to another, and sometimes even introduces other problems in the process.

I first noticed this in a Web Forms application, where our resident Best Practices Guy berated me for using inline data binding expressions in the .aspx files. These were actually simple data binding expressions, with no business logic, a bit like this:

<asp:Repeater id="rptData" runat="server">
    <asp:Label Text="<%# Eval("Text") %>" runat="server" />

Just like you’ve seen in every Web Forms tutorial since 2001, but he said I should have been looking up the label in the DataBound event and assigning it there instead:

void rptData_DataBound(object sender, RepeaterItemEventArgs e)
    var label = e.Item.FindControl("lblParagraph") as Label;
    if (label != null)
        label.Text = ((LineItem)e.Item.DataItem).Text;

He claimed that it would prevent problems if I’d mistyped the property name in the .aspx file, because the C# compiler would catch it.

The reason this is a fallacy is that it just moves the problem into your code-behind file. You’re just as likely to mistype the name of the control — lblParagraph — in the string and end up with exactly the same problem. Only it’ll be easier to miss it in testing because the null check means that it will fail silently. On top of that, you’re using more than twice as many lines of code spread over two different files rather than just one to do the same thing.

I noticed a similar problem when I was evaluating OOCSS — a design pattern that’s supposed to reduce duplication in your CSS, by having you declare separate CSS classes for different functional aspects such as “button” or “highlighted” or “media”. Twitter Bootstrap uses it fairly heavily. Its selling point is that it’s supposed to make your CSS more maintainable and lightweight without using a pre-processor by reducing duplication in your stylesheets. Unfortunately, in the process, it introduces a lot of duplication and weight into your HTML because you now have to set additional class declarations on a huge number of elements.

Then of course there’s our old friend, the Repository Facade, whose proponents tell you that it reduces tight coupling between your business layer and your ORM. Of course a generic Repository Facade does this at the expense of making it impossible to optimise your queries for performance, but with a specialised one — where you’re moving your queries into your Repository Facade itself — you’re just moving the tight coupling from one part of your codebase to another. It doesn’t reduce the amount of work that you would have to do to switch your data source in the slightest, and in the process it prevents you from unit testing your business logic independently of the database.

Your best practices are (probably) nothing of the sort

Now I have nothing against best practices per se.

But if you are going to tell me that something is a “best practice,” please first make sure that it really is a best practice. The software development world is plagued by so-called “best practices” that are nothing of the sort, that just introduce friction, ceremony and even risk without offering any benefits whatsoever in return. Some of them were once perfectly valid but have been superseded by developments in technology; some of them were based on widely held assumptions that have since been proven to be incorrect; some of them are based on total misunderstandings of something that someone famous once said; and some of them are just spurious.

I’ll give one example here, which came up in a discussion on Twitter the other day. It’s quite common for people to put their interfaces in one assembly, their business logic in a second, their repositories in a third, their models in a fourth, their front end in a fifth, and so on. This is all done in the name of “having a layered architecture.” The problem with this is that it makes dependency management harder (in fact in the pre-NuGet days it was an absolute nightmare) and forces you to jump around all over the place in your solution when making changes to related classes. It just adds friction, without even solving the problem it claims to solve: separate assemblies are neither necessary nor sufficient for a layered architecture. Oh, and it also violates the Common Closure Principle, which states that classes that change together must be packaged together.

Unfortunately, these so-called “best practices” proliferate because most developers lack the courage to question them, for fear of being viewed as incompetent or inexperienced by those with the authority to hire, fire or promote them. The people who promote garbage “best practices” tend to have Many Years Of Experience At Very Impressive Sounding Companies, and if you’re not that experienced (or confident) yourself, that can be quite intimidating. You don’t agree that we should put our interfaces, enums, business classes, repositories and presentation layers in separate assemblies? You obviously don’t understand a layered architecture!

Don’t let that intimidate you though. When somebody tells you that “you’re not following best practices,” it’s an indication that in their case, Many Years Of Experience At Very Impressive Sounding Companies actually means one year of experience repeated many times building run of the mill CRUD applications on outdated technologies at places that store users’ passwords in plain text. They are almost certainly not active on GitHub, or Twitter, or Stack Overflow, they are very unlikely to have hobby projects, and they probably never discuss programming with experts from outside their own team, let alone from other technology stacks.

In other words, The Emperor Has No Clothes.

But when something really is a best practice, it’ll be quite different. For starters, they will cite the practice concerned by name. They won’t tell you that “you’re not following best practices” but that “you’re violating the Single Responsibility Principle” or “you’re making test driven development harder” or “You Ain’t Gonna Need It” or something else specific. Another hallmark of a genuine best practice is that it will have tangible, enumerable benefits that are actually relevant to your situation. Here are some questions you can and should ask about it:

  1. Does it make it easier to get things right?
  2. Does it make it harder to get things wrong?
  3. Does it make it easier to back out when things go wrong?
  4. Does it make it easier to diagnose problems?
  5. Does it make it easier to get things done faster and with less effort without compromising points 1-4?
  6. Does it deliver the benefits that it claims to deliver? What evidence do you have that it does?
  7. Does it solve problems that you are actually likely to face, or is it one big YAGNI-fest?
  8. Are the problems that it solves still relevant, taking into account the current state of technology, market forces, security threats, and legislative or regulatory requirements?
  9. What alternative approaches have you considered, and how did they compare? It’s nonsensical to talk about “best practices” when you have nothing to compare them against, because the word “best” is meaningless in a sample size of one.
  10. Do its benefits actually outweigh its costs? In practice? In your situation?
  11. Have you understood it correctly, and are you sure you’re not confusing it with something else?

Any best practice that is worth following will stand up to scrutiny. And scrutinised it should be. Because blindly doing something just because somebody cries “best practices” is just cargo cult. And cargo cult programming is never a best practice.

Arguments that annoy me

There are certain arguments that crop up repeatedly in programming discussions that really, really get up my nose, because while they sound very authoritative and convincing at first glance, in reality they are nothing but hand-waving, or, as Shakespeare said, full of sound and fury, signifying nothing. These are they.

1. “I’ve never had a problem with it.”

This one usually crops up with proposals that are widely regarded as antipatterns, such as Pokémon exception handling or comparison-based database migrations.

There are several reasons why you might never have had a problem with something like that:

  1. You got lucky.
  2. You haven’t had enough experience with it.
  3. You don’t consider inefficiency, unreliability or inflexibility to be a problem.
  4. The problems that it caused were outside your area of competence, but affected other team members.
  5. The problems that it caused were under the surface and only materialised later.
  6. You did have problems but just thought they were a fact of life.

Someone once said that they always ask candidates in job interviews what their pain points are with their favourite technologies, because that’s the way to tell if someone really understands something and isn’t just fanboying it or CV stuffing. Until you’ve discovered the pitfalls of something, you haven’t really understood it properly, and you are in no position to advise on how to use it safely.

2. “These are the problems you might encounter if you get it wrong.”

This argument annoys me because it is nothing but FUD, and it is possible to come up with similar arguments against everything. It also completely misses the point.

The important question is not how easy it is to get something wrong, but how easy it is to get it right. Very often the people spouting this argument are promoting an alternative technology that fails very, very badly in the “getting it right” department. For example, a few years ago, the Subversion guys were pushing the Git-promotes-antisocial-development line for all it’s worth, yet they had no convincing answers for how to branch and merge properly, or how often you should check in code, or how to recover when svn update resulted in so many conflicts that you lost an entire day’s work, or what to do when you come to the end of a sprint and some items are done but others aren’t.

Thankfully, you don’t hear those objections so much now that Git has become mainstream and is the second most widely used VCS tool on the market: all the evidence indicates that those fears were unfounded. These days, it’s the anti-CSS preprocessor brigade that trot it out, with all sorts of FUD about bloated generated stylesheets and over-specificity (problems that are not hard to correct), yet they have no convincing answer to the critical questions of how you avoid DRY violations and magic numbers, and often propose laughably bad practices as an alternative.

Heck, a ladder can put you in a wheelchair if you get it wrong. A chainsaw can kill you if you get it wrong. A car can kill multiple people if you get it wrong. But we don’t stop using ladders and chainsaws and cars. We just take more care to get them right.

3. “It’s a leaky abstraction.”

This is another one of those FUD-that-can-apply-to-everything arguments. People are way too glib about dismissing abstractions as leaky, but if you read Joel Spolsky’s original article where he coined the term, he points out that every abstraction is leaky. Even the most elementary data types you work with are leaky abstractions on top of bits and bytes (which is why Steve Yegge categorises bits and bytes as one of the five essential areas of knowledge you should probe in a phone screen). Dates and times are a leaky abstraction. Unicode is a leaky abstraction. Even arithmetic is a leaky abstraction: if you’ve ever encountered overflow or rounding errors, you’ll know what I mean.

To be sure, some abstractions are leakier than others, but the important question about an abstraction is whether the benefits outweigh the problems introduced by the leaks. Another factor is how easy it is to avoid the problems that the leaks introduce simply by being aware of them. O/R mappers are a much debated example these days. People will tell you that if you’re not careful you’ll end up with all sorts of nasty select n+1 performance problems, but in reality, if you know what select n+1 means and why it’s a problem, it’s not hard to avoid it. In this case, the argument is just another example of “these are the problems you might encounter if you get it wrong” — see point 2 above.

The fact that an abstraction is leaky does not mean you need to avoid it. It merely means you need to understand it.