Introduction

In this article, we’ll see an example of method extraction. We’ll make our code less repetitive, cleaner, and easier to follow.

Duplicating a Method

Let’s take the SanitizeCrypto() function from our former fizz-env module:

func (e FizzEnv) SanitizeCrypto() {
	sanitizeCommon(e)

	v := reflect.ValueOf(e.Crypto)

	for i := 0; i < v.NumField(); i++ {
		val := v.Field(i).String()
		name := v.Type().Field(i).Name

		if val == "" {
			panic(
				fmt.Sprintf(
					"The environment variable for" +
					"'%s' is not defined.", name,
				),
			)
		}
	}
}

What if, along with e.Crypto, we also have… say… an e.Log member that requires the same kind of sanitization. We can come up with a SanitizeLog() method like this:

func (e FizzEnv) SanitizeLog() { // <-- (1)
	sanitizeCommon(e)

	v := reflect.ValueOf(e.Log) // <-- (2)

	for i := 0; i < v.NumField(); i++ {
		val := v.Field(i).String()
		name := v.Type().Field(i).Name

		if val == "" {
			panic(
				fmt.Sprintf(
					"The environment variable for " +
					"'%s' is not defined.", name,
				),
			)
		}
	}
}

If you look closely at the replicated SanitizeLog() function, aside from the method name and the attribute e.Log that we are reflecting upon ((1) and (2) in the source code, respectively), we are repeating the majority of the code.

These kind of copy/paste method duplications is a fact of development life. They happen all the time. It’s your responsibility, as a developer, to be vigilant and defend the code quality.

Extracting the Repetitive Code Piece

Instead, we can extract the for loop into its own method like this:

func sanitize(v reflect.Value) {
	for i := 0; i < v.NumField(); i++ {
		val := v.Field(i).String()
		name := v.Type().Field(i).Name

		if val == "" {
			panic(
				fmt.Sprintf(
					"The environment variable for " +
					"'%s' is not defined.", name,
				),
			)
		}
	}
}

Calling the Extracted Function

Then we can call it in our methods:

func (e FizzEnv) SanitizeCrypto() {
	sanitize(reflect.ValueOf(e.Crypto))
}

func (e FizzEnv) SanitizeLog() {
	sanitize(reflect.ValueOf(e.Log))
}

Conclusion

Much cleaner, isn’t it?

This approach is known as method extracting, and when done with a purpose, it can make your code clearer to read, leaner, and more maintainable.

Just keep in mind that each method extraction is an additional level of indirection. So if you abuse it, you will make your code harder to read (instead of easier), harder to follow, and harder to reason about.

Do this refactoring only when it makes sense. Do it only when the benefits of doing it (i.e., not repeating your code) outweigh the liabilities of doing it (i.e., an additional level of indirection, making code harder to follow).

Sometimes a little copying is better than a little dependency.

Read the Source

Below, you’ll find the zip archives of the projects and other related artifacts that we’ve covered in this article.

Enjoy… And may the source be with you 🦄.