Files

4.6 KiB
Raw Permalink Blame History

Script: A Trick for Designing Friendly APIs in Go


The Hook (The Pain)

[VISUAL: Face Camera]

"There is nothing worse than opening a codebase, finding a constructor, and seeing a list of magic numbers and boolean flags."

"You know the ones. NewServer(true, 30, false)."

"What is true? What is 30? Is false good or bad? You have to go read the definition just to understand the call site. Its a bad developer experience."


The Problem

[VISUAL: Slide 2 - The Optional Configuration Problem]

"This is the problem we are trying to solve. The 'Optional Configuration' problem."

"Go doesn't support method overloading like C# or Java. So when you write a constructor like NewServer, youre stuck with that signature."

"If you have optional parameters—like a timeout or a verbose flag—you end up forcing the user to pass 0 or nil or true just to satisfy the compiler."


The Breaking Change

[VISUAL: Slide 3 - "We need to add a TLS parameter"]

"And it gets worse when requirements change. Let's say six months later, you need to add TLS support."

"If you just add a parameter to the function, you have now broken every single place in your application where NewServer was called. You have to go refactor the entire world just to add one optional setting."


The "Okay" Alternative

[VISUAL: Slide 4 - "Just use a struct"]

"Now, the common response is: 'Just use a config struct.'"

"And honestly? This is fine. It's better than magic numbers. But it has a flaw: Ambiguity around zero values."

"If I pass Port: 0 in that config... do I mean 'Please bind to a random available port'? Or do I mean 'I forgot to set this, please use the default port 80'?"

"Distinguishing between 'default' and 'intentionally zero' is annoying with structs."


The Solution

[VISUAL: Slide 5 - "Nah bro, functional options"]

"So, the Go community settled on a better pattern. Functional Options."

"It looks a little scary at first because it uses closures, but once you get it, youll never go back."


Step 1: The Type

[VISUAL: Slide 6 - 1. Define the option]

"Step one: We define our Server struct, but we keep the fields unexported."

"Then, we define a type—let's call it Option. An Option is just a function that takes a pointer to your Server and modifies it. It returns nothing."


Step 2: The Closures

[VISUAL: Slide 7 - 2. Create closures]

"Step two: We create 'Constructor' functions that return that Option type."

"This is where the magic happens. Look at WithPort. It takes an integer, and it returns a closure that captures that integer."

"When that inner function eventually runs, it will apply that specific port to the server."


2:45 - Step 3: The Variadic Constructor

[VISUAL: Slide 8 - 3. Get variadic]

"Step three: The Constructor."

"Instead of taking a fixed list of arguments, we take a variadic slice of our Option type."

[CLICK to reveal the implementation]

"Inside the constructor, we do three things:"

  1. "We initialize the server with sensible defaults. (Host is localhost, Port is 80)."
  2. "We loop through the provided options and execute them. This overwrites the defaults with whatever the user provided."
  3. "We return the server."

The Result

[VISUAL: Slide 9 - Almost as good as C#]

"The result is a beautiful API usage."

"For my C# friends out there, this feels a lot like Named Parameters. It reads like a sentence: NewServer, WithHost, WithPort."

"I don't need to know what the default timeout is. I don't need to pass nil for things I don't care about. I just declare what I want to change."


Extensibility

[VISUAL: Slide 10 - Extensibility? We got you.]

"But the killer feature is extensibility."

[CLICK to reveal WithTLS]

"Let's go back to that TLS requirement. I can add a WithTLS option function..."

[CLICK to reveal usage]

"...and I don't break a single existing line of code. srv (the first one) still compiles perfectly. srv2 uses the new feature. Backward compatibility is preserved."


Summary

[VISUAL: Slide 11 - So why use this pattern?]

"To recap, why should you use this?"

[CLICK] "1. Defaults are easy. You define them once in the constructor, and the user never has to think about them." [CLICK] "2. It's self-documenting. No more true, false, 30." [CLICK] "3. It's future-proof. You can add new options forever without breaking your consumers."


Outro

[VISUAL: Slide 12 - Go forth...]

"This is the standard for a reason. If you're building a library or a package in Go, treat your users nicely. Use Functional Options."

"I'll see you in the next one."