blog.8-p.info

Dave Cheney already has written Functional options for friendly APIs in 2014. I don’t have much to add.

I initially saw this pattern as Go’s named parameters alternative. But this pattern also works as an alternative for function overloading.

Changing a function signature in a large codebase is always hard regardless of programing languages. I often use function overloading to make the signature backward-compatible, but I cannot do that in Go. Functional options come in handy as a way to have extensible APIs.

FYI, Google researchers have published Detecting argument selection defects in 2014. According to the research paper;

We were able to show that the probability of an argument selection defect increases markedly beyond 5 parameters. One option for methods with many parameters is to redesign using a fluent interface or parameter objects. Another alternative is the use of named parameters, which could be emulated in Java with a static check in Error Prone which inspects the comments on arguments.

So, if your Go function takes more than 5 parameters, consider functional options.

amazon-ecr-containerd-resolver defines ecrAPI as AWS SDK’s subset.

// ecrAPI contains only the ECR APIs that are called by the resolver
// See https://docs.aws.amazon.com/sdk-for-go/api/service/ecr/ecriface/ for the
// full interface from the SDK.
type ecrAPI interface {
	BatchGetImageWithContext(aws.Context, *ecr.BatchGetImageInput, ...request.Option) (*ecr.BatchGetImageOutput, error)
	GetDownloadUrlForLayerWithContext(aws.Context, *ecr.GetDownloadUrlForLayerInput, ...request.Option) (*ecr.GetDownloadUrlForLayerOutput, error)
	BatchCheckLayerAvailabilityWithContext(aws.Context, *ecr.BatchCheckLayerAvailabilityInput, ...request.Option) (*ecr.BatchCheckLayerAvailabilityOutput, error)
	InitiateLayerUpload(*ecr.InitiateLayerUploadInput) (*ecr.InitiateLayerUploadOutput, error)
	UploadLayerPart(*ecr.UploadLayerPartInput) (*ecr.UploadLayerPartOutput, error)
	CompleteLayerUpload(*ecr.CompleteLayerUploadInput) (*ecr.CompleteLayerUploadOutput, error)
	PutImageWithContext(aws.Context, *ecr.PutImageInput, ...request.Option) (*ecr.PutImageOutput, error)
}

Defining a smaller, limited interface is always nice. It clearly shows what your code needs. However, in interface-first languages such as Java, having a smaller interface means you need to define your own wrapper interface. Go’s interface provides a better alternative that doesn’t add an unnecessary abstraction layer.

In addition to that, small interface is easier to write test doubles. While tools like gomock makes mocking a big interface easier, pre-programmed mocks are cumbersome to maintain. Other test doubles such as fake objects are often useful to make sure your implementation correctly call its collaborators.

2020-12-20

I’ve found Dave Cheney have written about this pattern as well.

Let callers define the interface they require

Interfaces declare the behaviour the caller requires not the behaviour the type will provide. Let callers define an interface that describes the behaviour they expect. The interface belongs to them, the consumer, not you.

Because of “modern” mocking libraries make mocking easier, developers mock stuff, sometimes too much. Mocking is a necessary evil. You would need to mock a few stuff, but only a few.

The biggest problem of mocking is that you run different code in your testing environment, which basically kills the value of your tests. The tests tell that your change is fine, but you would be unsure because your dependencies are all mocked.

Another problem is that mocking could let you specify your implementation in detail. You know that this function should call A, B, and C. But this level of detail would change over time. Hence it would be better to not specify the detail.

Good tests allow you to change your function easily because the tests tell you that your change is fine. Bad tests prevent you to change your function because you have to modify all tests first.

This is another Go thing I’ve learned at work.

os.Getenv() and os.Setenv() call syscall.Getenv() and syscall.Setenv(). And inside the syscall package, there is a package-scope lock.

This is the 60th post of my #100DaysToOffload challenge. I was fast this time.

More Pictures?

No. I think I took pictures more than before, but most of them are about my family, which I don’t want to share here.

In addition to that, I like to take pictures under natural light, but I mainly work at that time. While software development doesn’t need sunlight much compared to photography, I work while others are virtually, time-wise around.

Post-Offload

What would I do on this blog after the challenge? I may slow down, but I know that I cannot simply say I’m going to prioritize quality over quantity. It would result dead-end.

Limiting the question to “this blog” doesn’t make much sense. What would I do on the internet, or well… my life?

This really sounds like a mid-life crisis though.