Martin Fowler in his book defines refactoring as “a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior”. This book contains a long list of refactorings techniques that mean to be applied under certain scenarios and with the intention to eliminate “code smells”.

Refactorings are a very extense topic and I’ve found them to have an important role in the software development process. Their relevance is such a high that they are an essential component for TDD life cycle.

Because of their importance, in this post I would like to share 4 of the refactoring techniques I’ve been used the most in my role as software developer, but before get started, since applying refactoring techniques can be automated (i.e. some IDEs provide you with tools to apply refactorings and make your life easier with a couple of clicks and selections), here I’ll describe them by doing it manually for Go language and I’ll try to follow them as a reference guide. Getting real, and consider this as a MUST sentence, our dev team is aware that before applying any refactoring technique source code observable funcionality should be covered with unit tests and get all of them passed.

Extract Method

This is a technique that I apply to my code frequently. It consists of extracting a piece of code that is grouped by intention and moved into a new method. I’ve found one strong reason to apply it which allows me to split a long method or function into small methods that group a piece of logic. Commonly, the name of the small method or function provides a better insight about what that piece of logic is.

Example below shows before and after applying this refactoring technique. My main goal was to abstract complexity by separating it in different functions.

func StringCalculator(exp string) int {
    if exp == "" {
        return 0
    }
    
    var sum int
    for _, number := range strings.Split(exp, ",") {
        n, err := strconv.Atoi(number)
        if err != nil {
            return 0
        }
        sum += n
    }
    return sum
}
func StringCalculator(exp string) int {
    if exp == "" {
        return 0
    }
	return sumAllNumberInExpression(exp)
}

func sumAllNumberInExpression(exp string) int {
    var sum int
    for _, number := range strings.Split(exp, ",") {
        sum += toInt(number)
    }
    return sum
}

func toInt(exp string) int {
    n, err := strconv.Atoi(exp)
    if err != nil {
        return 0
    }
    return n
}

StringCalculator function turned out to be simpler but it creates more complexity when new two methods are added. This is a sacrifice I’m willing to take with a deliberate decision. I follow this as a reference instead of a rule, in the sense that knowing what is the consequence of applying a refactoring technique can be a good judgement for you whether to apply it or not.

Move Method

Sometimes after I’ve applied extract method refactoring, I find myself asking another question: should this method belong to this struct or package? Move Method is a simple technique that consists of (as its name implies), moving a method from one struct to another. One trick I’ve identified for myself to make sure if a method should belong to that struct is by figuring out if that method accesses the internals of another struct dependency more than its own. Take a look at the example bellow:

type Book struct {
    ID    int
    Title string
}

type Books []Book

type User struct {
    ID    int
    Name  string
    Books Books
}

func (u User) Info() {
    fmt.Printf("ID:%d - Name:%s", u.ID, u.Name)
    fmt.Printf("Books:%d", len(u.Books))
    fmt.Printf("Books titles: %s", u.BooksTitles())
}

func (u User) BooksTitles() string {
    var titles []string
    for _, book := range u.Books {
        titles = append(titles, book.Title)
    }
    return strings.Join(titles, ",")
}

As you can see User’s method BooksTitles uses the internal fields from books(i.e Title) more than User's, that’s a sign this method should be owned by Books instead. Let’s apply this refactoring technique to move this method to Books type and then be called by user’s Info method.

func (b Books) Titles() string {
    var titles []string
    for _, book := range b {
        titles = append(titles, book.Title)
    }
    return strings.Join(titles, ",")
}

func (u User) Info() {
    fmt.Printf("ID:%d - Name:%s", u.ID, u.Name)
    fmt.Printf("Books:%d", len(u.Books))
    fmt.Printf("Books titles: %s", u.Books.Titles())
}

After applying this method Books type gains more cohesion because it is the only one who owns control and access to its fields and internal properties. Again, this is a thought process that precedes a deliberate action, knowing what will be the consequences of applying the refactoring.

Introduce Parameter Object

How many times have you seen a large list of parameters in a method like this?

func (om *OrderManager) Filter(startDate, endDate time.Time, country, state, city, status string) (Orders, error) {
    ...

Even we do not see the code inside the function, when we see a large number of parameters like this, we can think about the large number of actions it does.

Sometimes, I find those parameters being highly related each other and used together later inside the method where they were defined. This refactoring provides a way to make that scenario be handled in a more object-oriented way and it suggests to group those parameters into a struct, replace the method signature to use that object as parameter and use that object inside the method.

type OrderFilter struct {
    StartDate time.Time
    EndDate   time.Time
    Country   string
    State     string
    City      string
    Status    string
}


func (om *OrderManager) Filter(of OrderFilter) (Orders, error) {
    // use of.StartDate, of.EndDate, of.Country, of.State, of.City, of.Status.

That looks cleaner and give an indentity about those parameter, however this would demand for me to change all references where this method is called and it would require create a new object of type OrderFilter as an argument before it gets passed to the method. Again, I try to be very thoughtful and think about the consequences before applying this refactoring. I consider this technique really efective when the level of impact in your code is very low.

Replace Magic Number with Symbolic Constant

This technique consist of the replacement of a hard-coded value with a constant variable to give it intention and meaningfulness.

func Add(input string) int {
    if input == "" {
        return 0
    }

    if strings.Contains(input, ";") {
        n1 := toNumber(input[:strings.Index(input, ";")])
        n2 := toNumber(input[strings.Index(input, ";")+1:])

        return n1 + n2
    }

    return toNumber(input)
}


func toNumber(input string) int {
    n, err := strconv.Atoi(input)
    if err != nil {
        return 0
    }
    return n
}

What does that ; character mean? If the answer is too ambiguous for me, I can create a temp variable and setup the value with the hard-coded character to give it an identity and intention.

func Add(input string) int {
    if input == "" {
        return 0
    }

    numberSeparator := ";"
    if strings.Contains(input, numberSeparator) {
        n1 := toNumber(input[:strings.Index(input, numberSeparator)])
        n2 := toNumber(input[strings.Index(input, numberSeparator)+1:])

        return n1 + n2
    }

    return toNumber(input)
}


func toNumber(input string) int {
    n, err := strconv.Atoi(input)
    if err != nil {
        return 0
    }
    return n
}

Wrapping up

Thank you for reading this post. I really hope you found it useful and interesting. Refactorings are a very extense topic and I’ve illustrated 4 of them that I use the most in my role as software developer. Don’t take what was mentioned here for granted and experiment it by yourself. The refactorings techniques described here are just used as guidelines and not followed as rules, meaning they are adapted with intention and purpose when needed. I would like to end this post by saying we are accountable for all code we write and all the tools we use, our experience and knowledge can guide us with the skills that works the best for every situation and I think refactoring techniques can be really worthy.