Live data from Hacker News

Designing Good Interfaces

pboyd.io

1–10 of 32 posts

Re: Designing Good Interfaces

#2
Most of the post resonated with me, but the initial analogy and its concrete example give me pause.

> The dependency (oil, in this example) is an argument, not because anyone cares to customize it, but to simplify the implementation.

This is a huge leap! I know it’s an example, but it’s perfectly reasonable to satisfy both “don’t make me bring oil to the grease monkeys” and “express oil as an explicit dependency of grease monkey activity.” It will, of course, buck some recent discussions here, but the reasonable solution to that is an additional abstraction. And it will of course not buck another long time favorite here: functional core.

1. Provide the convenience interface which defaults to some way of determining a sensible default. In the example case, bringing your own highly configurable oil to the people who change your oil is an exceedingly idiosyncratic case, but for generalization purposes let’s say that’s optional.

2. For all cases, directly provide oil to the processes explicitly dependent on oil to proceed.

+ 3. Make sure you have your dependencies: if the weirdo with super weird oil opinions supplied their own weird oil, you’re done; otherwise get your sensible defaults ready. This is super cool because,

+ 4. Now you can just put oil in the car without making the oil selection everyone’s business (responsibility).

Maybe that’s “simplify[ing] the implementation”, but not in the ways that’s usually meant. It also has the really awesome property of preventing billion dollar mistakes. This:

> Leave the argument nil, and the function will silently leave the object in a bad state.

Doesn’t have to happen, even if your language/environment is predisposed to it. For the cost of a fairly mundane function boundary, you get a convenient interface for users who don’t care about how the oil sausage is made, and free null safety.

> What the caller probably wanted was more like this

I really don’t think that’s a reasonable assumption at all. What the caller probably wanted was more like this:

  func ChangeOil(c Car, oilType OilType) error {
    // There, that’s the whole function body
  }
They don’t care if you also provide a good interface to the mechanics, they care about you managing a business (abstraction) they hired you to take care of for them.

Re: Designing Good Interfaces

#3

Most of the post resonated with me, but the initial analogy and its concrete example give me pause. > The dependency (oil, in this example) is an argument, not because anyone cares to customize it, but to simplify the implementation. This is a huge leap ! I know it’s an example, but it’s perfectly reasonable to satisfy both “don’t make me bring oil to the grease monkeys” and “express oil as an explicit dependency of…

Go even has a mantra for this: “accept interfaces, return concrete types”. In the example, I would think to design it such that Oil is just an interface with adapters for either user specified oilType or oilInventory or whatever.

Re: Designing Good Interfaces

#5
My first question was, where does “inventory.GetOil()” come from. Is this just a package that wasn’t mentioned? Not being a Go expert, are packages typically global like this? Would that be a good way to implement this function/method (in a package that then becomes available everywhere in main() as opposed to passing it)?

Ah, well, maybe I missed the point entirely.

Re: Designing Good Interfaces

#6
Good interfaces should primarily have reasonable and well documented pre and post conditions. This is very apparently not so in the oil change example in the article.

It's completely stunning to me how frequently this is forgotten in spite of having been a key component of object oriented programming when it was invented (ie smalltalk days, before I was born).

Re: Designing Good Interfaces

#7
post #5

My first question was, where does “inventory.GetOil()” come from. Is this just a package that wasn’t mentioned? Not being a Go expert, are packages typically global like this? Would that be a good way to implement this function/method (in a package that then becomes available everywhere in main() as opposed to passing it)? Ah, well, maybe I missed the point entirely.

After writing almost entirely functional code for the last 10 years, I find it really hard to read and reason about anything that looks like this.

Re: Designing Good Interfaces

#9
post #5

My first question was, where does “inventory.GetOil()” come from. Is this just a package that wasn’t mentioned? Not being a Go expert, are packages typically global like this? Would that be a good way to implement this function/method (in a package that then becomes available everywhere in main() as opposed to passing it)? Ah, well, maybe I missed the point entirely.

I don’t write Go, so I’m similarly not qualified to address how this particular implementation satisfies the dependency. But my hunch is that it’s implicitly an import of a singleton. Which is basically a global, but a fairly common Java-ish pattern for encapsulation of shared dependencies.

It’s not even a terrible pattern if you’ve already accepted shared dependencies as a thing (which you have to do in reality), but it’s much easier to reason about if you isolate them to something that provides explicit dependencies where your logic happens.

Re: Designing Good Interfaces

#10
post #6

Good interfaces should primarily have reasonable and well documented pre and post conditions. This is very apparently not so in the oil change example in the article. It's completely stunning to me how frequently this is forgotten in spite of having been a key component of object oriented programming when it was invented (ie smalltalk days, before I was born).

Can you clarify what you mean by this?
Post reply on HN