Picture of Stefan Lieser
Stefan Lieser

Simple refactorings - Part 3

This article is a revised version. It was previously published at refactoring-legacy-code.net published.

This is a series of articles. You can find the other articles here:

Simplify if statements - Extract Method

Another area of application for the Extract Method Refactoring are conditions of branches. The following listing shows an excerpt from an alarm clock application.

if (DateTime.Now >= wakeupTime) {
    using (var soundPlayer = new SoundPlayer(@"c:\Windows\Media\chimes.wav")) {
        soundPlayer.Play();
    }
    timer.Stop();
}

The condition of the if-statement becomes clear when we read the content of the branch. A sound file is played and a timer is stopped, so that everything looks as if this code will be executed when the wake-up time is reached. Compare the original code with the following variant:

if (AlarmTimeReached()) {
    using (var soundPlayer = new SoundPlayer(@"c:\Windows\Media\chimes.wav")) {
        soundPlayer.Play();
    }
    timer.Stop();
}


private bool WakeTimeReached() {
    return DateTime.Now >= weckZeit;
}

Now I can change the condition of the if-statement and immediately understand the meaning of the branch. Here again, another level of abstraction has been introduced, allowing the reader to maintain the level of abstraction or descend into the details. The reader has the choice of what they did not have before the refactoring. In the example, however, there is still a violation of the principle Single Level of Abstraction. The condition of the branch is now at a high abstraction level, while the body of the conditional execution is at a low abstraction level. Another Extract Method adjusts the levels:

if (AlarmTimeReached()) {
    Wake();
}

Conclusion

Use Extract Methodin order to Condition one if statement easier to understand. The higher Level of abstraction makes the condition for the reader more legibleprovided the name is well chosen. Furthermore, Extract Method offers the opportunity to create a DRY injury to fixi.e. duplication can be eliminated.

Make inputs visible - Introduce parameters

In order to understand a method, the inputs and outputs of the method must be known. The first glance is usually at the signature of the method. The return type of the method makes it clear whether the method returns a result and what type it is. The method parameters make it clear what the inputs of the method are. The input values are used by the method to create the output value - EVA - input, processing, output. This can be easily seen in the following listing:

public int sum(int[] values)

Based on this signature, we expect the method to add up the values from the integer array and return the result. What is your expectation when you see the following signature?

public int Sum()

Based on the signature alone, it remains unclear how the method works. With the knowledge of object orientation, we as developers can guess that this method will work on the state of the class. Here is the entire listing:

public class Calculator
{
    private readonly int[] _values;

    public Calculator(int[] values) {
        _values = values;
    }

    public int sum() {
        var result = 0;
        foreach (var i in _values) {
            result += i;
        }
        return result;
    }
}

When reading the complete source code, it becomes clear that the values to be added are passed into the class via the constructor. The inputs of the method Total therefore consist not only of the parameters but also of the fields of the class. Sometimes methods have to work on the state of the class. However, where this can be avoided, the inputs should all be passed as parameters, as this makes the method easier to understand. It is then not necessary to look at the entire class, but an isolated look at the method is sufficient. The section of code to be viewed is smaller. Furthermore, the mechanism is simpler. A reader does not need to know the constructor and the field before understanding how the method works. An isolated view of the method is sufficient.

With the help of the Introduce parameters refactorings, the method can be changed so that the required values are passed in as parameters. To do this, select the field in the method and start the Introduce parameters Refactoring.

public int Sum() {
    var result = 0;
    foreach (var i in _values) {
        result += i;
    }
    return result;
}

The IDE adds a parameter of the type of the field, here int[]in the method signature and then tries to call all callers of the method Total the field values as a parameter. It should be clear at this point that this refactoring is often limited to the visibility range of a class. The replacement can be carried out within the class. Outside the class, the field of the class can generally only be used for callers if it is public is.

Another example should illustrate how difficult it is to understand methods that work on fields of the class as input in addition to their parameters:

public string[] CsvTabulate(string[] csvRows) {
    _csvRows = csvRows;

    RecordsCreate();
    int[] widths = ColumnWidthDetermine();
    TableCreate(widths);

    return _table;
}

The method CsvTabulation receives a Array from Strings as input. Each string in this array is a CSV line. As a result, the method returns a nicely formatted table of these CSV rows, again as an array of strings.

We can guess how the solution works from the methods called. Obviously, a set of records is created from the CSV rows. The widths of the individual columns are then determined in order to finally create the table. It is unclear how the values are transferred to the individual methods. The method RecordsCreate has neither parameters nor does it return a result (unless this is ignored, which is syntactically permitted with C#). The previous line shows that the input is stored in a field. We assume that it is a field based on the naming convention: the identifier begins with an underscore and is therefore likely to be a field of the class.

Obviously, the values are transferred here via fields of the class. The result of RecordsCreate will probably also be created via a field of the class an Determine column widths is passed. With the method Create table both transfer methods, via field and via parameter, are then mixed. The example is constructed... However, you will probably find such a procedure in your code base. By Introduce parameters refactorings, the methods can be easily modified so that all required input is passed to the methods via parameters. The results are as follows:

public string[] CsvTabulate(string[] csvRows) {
    _csvRows = csvRows;

    RecordsCreate(_csvRows);
    int[] widths = ColumnWidthDetermine(_records);
    TableCreate(widths, _records);

    return _table;
}

The methods now receive all required inputs as parameters. The outputs of RecordsCreate and Create table however, are still passed on via fields. Unfortunately, automated refactoring does not help here; the subsequent change must be made manually. The outputs of the methods have to be passed with return are returned instead of storing the values in fields. As a result, the method looks as follows after the refactorings:

public string[] CsvTabulate(string[] csvRows) {
    var records = RecordsCreate(csvRows);
    int[] widths = ColumnWidthDetermine(records);
    var table = TableCreate(widths, records);

    return table;
}

Conclusion

Use Introduce parametersto include as many Inputs a method in the Parameter list to record. This makes the method easier to understand.

Our seminars

course
Clean Code Developer Basics

Principles and tests - The seminar is aimed at software developers who are just starting to deal with the topic of software quality. The most important principles and practices of the Clean Code Developer Initiative are taught.

to the seminar "
course
Clean Code Developer Trainer

Conducting seminars as a trainer - This seminar is aimed at software developers who would like to pass on their knowledge of Clean Code Developer principles and practices or Flow Design to others as a trainer.

to the seminar "
course
Clean Code Developer CoWorking

Online CoWorking incl. coaching -
We are often asked what developers can do to keep up with the topic of clean code development. Our answer: Meet up with other Clean Code Developers online on a regular weekly basis.

to the seminar "

Leave a Comment

Your email address will not be published. Required fields are marked *

en_USEnglish