
Working with Go functions that have numerous parameters can be overwhelming, but there are ways to tame the beast. A Go function can accept up to 65536 parameters.
You can pass a slice of arguments to a function, which can be a more elegant solution than listing out each argument individually. This is especially useful when you need to pass a large number of parameters.
In Go, you can use the ... operator to capture a variable number of arguments into a slice. This can help simplify your code and make it more readable.
Intriguing read: How Much Sambucol Should I Take?
Problem and Solution
The problem with Go functions having many parameters is that it can make the code harder to read and understand.
Having a large number of parameters can lead to cluttered function signatures, making it difficult to see what the function actually does.
To solve this issue, we can hide such 'huge' parameters behind an interface.
Problem Statement
The codebase I worked with had a very low unit test coverage, and it was a real challenge to write test cases.
It was full of plain functions with many parameters, more than 5 in some cases.
Some of these parameters brought in a lot of dependencies, making it difficult to mock them.
These dependencies were often alien functions that we needed to use to access information within the parameters.
This made it hard to write test cases, and it's no wonder the test coverage was low.
Solve the Issue
Solving the issue requires a creative approach. By introducing a method of hiding huge parameters behind an interface, we can simplify complex code.
This method involves encapsulating parameters within an interface, making it easier to manage and modify them. The interface acts as a barrier, shielding the underlying complexity from the rest of the code.
To implement this solution, we need to design an interface that encapsulates the huge parameters. This interface should provide a simple and intuitive way to access and modify the parameters.

By doing so, we can reduce the impact of huge parameters on our code's maintainability and scalability. The interface becomes a single point of access, making it easier to manage and update the parameters.
This approach has been successfully applied in various situations, where huge parameters were causing issues with code complexity. By hiding these parameters behind an interface, developers were able to simplify their code and improve its overall quality.
Alternative Approaches
Using pointers to modify an incoming struct is an alternative to reassigning variables, but it's not preferred as it can lead to mutable objects and side-effect functions.
This approach is more in-line with how it's typically seen in Go, as demonstrated by an example that uses pointers to modify a struct.
Variadic functions are another option for handling multiple parameters, allowing a function to take between 0 and N parameters, even though only one is used.
Alternative Currying Implementation
In some programming languages, like Go, a different approach to currying is used, which involves modifying the incoming struct instead of returning a copy of it.
This method is more in-line with Go's typical way of handling structs, as seen in the alternative currying implementation example.
Modifying the incoming struct can be a more efficient approach, especially when dealing with large data structures.
However, this method can make the code less predictable and harder to reason about, especially when working with multiple functions that modify the same struct.
Personally, I prefer immutable objects and side-effect free functions, but that's just a matter of personal preference.
Wrapper Functions
Wrapper functions can make a codebase more intuitive by providing a clear API for common use cases.
The strings package is a good example, where strings.ReplaceAll() provides a default implementation that matches the behavior of PHP and Python string replace functions.
Sometimes, a natural name for a wrapper function isn't obvious, as seen with the password.Hash function and its optional cost parameter.
HashDefault() doesn't sound like a friendly function name, and flipping the names to Hash(password []byte) and HashWithCost(password []byte, cost int) doesn't feel great either.
Check this out: Azure Functions Cost
Variadic Function Handling

Go's variadic functions are a good alternative to default arguments, making it easy to write functions that can take a variable number of parameters.
This approach is particularly useful for functions with a single optional parameter, where the caller can simply ignore it without affecting the function's behavior.
However, things get tricky when multiple optional parameters are needed, requiring each parameter to be of a different type and searched for in the passed slice by type.
In such cases, the code can become unidiomatic and fight against the language's limitations, as seen in Rob Pike's Upspin project and my own currency.Formatter example.
A fresh viewpoint: Nextjs Url Parameter Is Not Allowed
Default Values and Constants
You can avoid cluttering your function calls with default values by introducing a default configuration. This can be achieved by creating an instance of your options struct with the default values.
By doing so, you can instantiate the default options before the iteration, making it easier to use default values.
Constants are another way to simplify function calls. The bcrypt package uses a constant for the default cost, making it easier for callers to use the function without specifying the cost.
For example, the xmath.Round() function could default to 0 digits and round up, with a constant for the default cost. This approach reduces verbosity.
However, using constants alone can become cumbersome when there are multiple optional parameters. A possible solution is to combine wrapper functions and constants.
The bojanz/currency package uses a simpler wrapper approach, where the DefaultDigits constant indicates the currency-specific value, such as 2 for USD and 0 for JPY. Callers can then pass options to override the default values.
Optional parameters can be put on their own struct, which is then passed to the function. A nil struct can be used to signal that defaults should be used. This approach requires using a pointer to options, which can be confusing.
A better approach is to define a default options struct, like alexedwards/argon2id does. This way, the caller must pass the default options struct, making it clear that defaults are being used.
Here's an interesting read: Golang Reflect to Call Function in Package
Featured Images: pexels.com


