---
# System prepended metadata

title: REFACTORING

---

# REFACTORING
## Code smells - Código duplicado
### Caso - 1

Neste exemplo temos dois métodos que apesar de serem diferirem, fazem o mesmo papel, ambos recebem "fields" e "labels" e verificam, se o field estiver vazio o label pode ser mostrado para o usuário. Eu fiz esta separação, pois um método recebe como parâmetro um "TextField" e outro um "PasswordField", mas pensando melhor, como a classe "PasswordField" é subclasse de "TextField" acredito que poderiamos utilizar apenas um método para ambos.
```java
    public static boolean textFieldsIsEmpty(List<TextField> listTf, List<Label> lLabel) {
        boolean test = false;
        for (int i = 0; i < listTf.size(); i++) {
            if (listTf.get(i).getText().isEmpty() || listTf.get(i).getText().replaceAll(" ", "").equals("")) {
                lLabel.get(i).setVisible(true);
                test = true;
            }
        }
        return test;
    }

    public static boolean passwordIsEmpty(PasswordField ps, Label error) {
        if (ps.getText().isEmpty() || ps.getText().replaceAll(" ", "").equals("")) {
            error.setVisible(true);
            return true;
        }
        return false;
    }
```

Para fazer este "refactoring" eu fiz a **remoção do código duplicado** e a **mudança de assinatura do método**, além da **renomeação de variáveis**.
.
```java
    public static boolean verifyFieldsAreEmpty(List<TextField> listFields, List<Label> listLabels) {
        boolean isEmpty = false;
        for (int i = 0; i < listFields.size(); i++) {
            if (listFields.get(i).getText().isBlank()) {
                listLabels.get(i).setVisible(true);
                isEmpty = true;
            }
        }
        return isEmpty;
    }

```

### Exemplo de funcionamento antes e depois.
<img src="https://i.imgur.com/Ftjwt5c.png" alt="drawing" width="700"/>
<img align="right" src="https://i.imgur.com/lAhYrPP.png" alt="drawing" width="700"/>



### Caso - 2
A seguir, temos um método duplicado em classes diferentes.

```java
    public class LoginViewController {
        /** **/
        private void restartLabels() {
                this.lbEmailError.setVisible(false);
                this.lbPasswordError.setVisible(false);
                this.lbPasswordError.setVisible(false);
        }
    }

    public class CreateAccountController {
        /** **/
        private void restartLabels() {
            this.lbNameError.setVisible(false);
            this.lbLastNameError.setVisible(false);
            this.lbEmailError.setVisible(false);
            this.lbPasswordError.setVisible(false);
        }

    }
```

Para resolver isto eu utilizei da técnica de **movimentação de métodos** e **alteração da assinatura do método**, neste caso, eu retirei estes métodos das suas respectivas classes e criei um método estático em uma classe utilitária para subustituí-los e poder reutiliza-lo em ambas as classes.

```java
    
    public class Utils {
        /** **/
        public static void restartLabels(List<Label> list) {
            for (Label label : list) {
                label.setVisible(false);
            }
        }
    }
```
## Code smells - Métodos longos
### Caso - 1
O método a seguir tem o objetivo de encontrar um número escolhido pelo usuário, com base em algumas perguntas. O mesmo está desnecessáriamente longo, logo é candidato a refatoração.

```java
    public static void findNumberGame(int beggin, int end){
        if(beggin >= end - 1){
            System.out.println("Número não encontrado");
            return;
        }
        int middleNumber = beggin + ((end - beggin) / 2);
        Boolean isGreater = false;
        Scanner in =  new Scanner(System.in);
        System.out.println("S para maior, N para menor  ou qualquer coisa se achar o número");
        System.out.print("O número é maior que: " + middleNumber + "? ");
        String response = in.nextLine();

        if(response.equals("S")) isGreater = true;
        else if(response.equals("N")) isGreater = false;
        else isGreater = null;

        if(isGreater == null) {
            System.out.println("O número que vocẽ escolheu é: " + middleNumber);
            return;
        }
        else if(isGreater)
            findNumberGame(middleNumber, end);
        else
            findNumberGame(beggin, middleNumber);
    }
```

Utilizando a técnica de **extração de métodos**, extraímos partes deste método e criamos mais dois. "makeQuestion" e "printQuestion". Agora temos um código mais "clean".

```java
    public static void findNumberGame(int beggin, int end){
        if(beggin >= end - 1){
            System.out.println("Número não encontrado");
            return;
        }
        int middleNumber = beggin + ((end - beggin) / 2);
        Boolean isGreater =  makeQuestion(middleNumber);

        if(isGreater == null) {
            System.out.println("O número que vocẽ escolheu é: " + middleNumber);
            return;
        }
        else if(isGreater)findNumberGame(middleNumber, end);
        else findNumberGame(beggin, middleNumber);
    }

    public static Boolean makeQuestion(int number){
        Scanner in =  new Scanner(System.in);
        printQuestion(number);
        String response = in.nextLine();
        if(response.equals("S")) return true;
        else if(response.equals("N")) return false;
        else return null;

    }

    public static void printQuestion(int number){
        System.out.println("S para maior, N para menor  ou qualquer coisa se achar o número");
        System.out.print("O número é maior que: " + number + "? ");
    }
```
### Caso - 2
No caso abaixo temos uma classe que tem como sua função receber um número e transformá-lo em um número romano. Exemplo: 3899 é MMMDCCCXCIX, 639 é DCXXXIX. O método que faz esta transformação está muito grande e como consequência, bastante complexo. Por isso iremos refatorar.

```java
public class RomanNumeralsEncoder {
    
    private final HashMap<Integer, String> romanValues = fillValues();
    private Integer decimalNumber;

    public RomanNumeralsEncoder(int number){
        this.decimalNumber = number;
    }

    public String toRoman(int n) {
            String roman = "";
            int holder = 0;
            int decimalNumberAux = n;
            HashMap<Integer, String> romanValues = fillValues();

            for (Integer keys : romanValues.keySet()) {
                double result = (double) decimalNumberAux / keys;
                int resultInt = (int) result;

                if(result - resultInt > 0.899){

                    int auxKey = 0;

                    if( keys == 5 || keys == 50 || keys == 500){
                        auxKey = keys - (keys - (keys / 5));
                     }else{
                        auxKey = keys - (keys - (keys / 10));
                    }

                    roman += romanValues.get(keys).repeat(resultInt) + romanValues.get(auxKey)
                    + romanValues.get(keys);
                    decimalNumberAux = decimalNumberAux - ((keys * resultInt) + (keys - auxKey));

                }else if(resultInt >= 1 && resultInt <= 3){
                    roman += romanValues.get(keys).repeat(resultInt);
                    decimalNumberAux -= (keys * resultInt);
                }else if(resultInt > 3){
                    roman += romanValues.get(keys) + romanValues.get(holder);
                    decimalNumberAux -= (holder - keys);

                }
                holder = keys;
        }
            return roman;
        }

         private HashMap<Integer, String> fillValues(){
            HashMap<Integer, String> romanValuesAux = new LinkedHashMap<>();
            romanValuesAux.put(1000,"M");
            romanValuesAux.put(500,"D");
            romanValuesAux.put(100,"C");
            romanValuesAux.put(50,"L");
            romanValuesAux.put(10,"X");
            romanValuesAux.put(5,"V");
            romanValuesAux.put(1,"I");
            return romanValuesAux;
        }
}
```
Utilizando a **extração de métodos** o código refatorado ficou assim:
```java
public class RomanNumeralsEncoder {
    private final HashMap<Integer, String> romanValues = fillValues();
    private Integer decimalNumber;

    public RomanNumeralsEncoder(int number){
        this.decimalNumber = number;
    }

    public String toRoman() {
        String roman = "";
        int holder = 0;
        Integer decimalNumberAux = this.decimalNumber;

        for (Integer keys : this.romanValues.keySet()) {
            double result = (double) decimalNumberAux / keys;
            int resultInt = (int) result;

            String [] values = handleResult(result, resultInt, roman, decimalNumberAux, keys, holder);
            roman = values[0];
            decimalNumberAux = Integer.parseInt(values[1]);
            holder = values.length > 2 ? Integer.parseInt(values[2]) : holder;

        }
        return roman;
    }

    private int setAuxKeyValue(int keys){
        int auxKey = 0;

        if (keys == 5 || keys == 50 || keys == 500) {
            auxKey = keys - (keys - (keys / 5));
        } else {
            auxKey = keys - (keys - (keys / 10));
        }

        return auxKey;
    }

    private String[] handleResult(double result, int resultInt, String roman, int decimalNumberAux, int keys, int holder){
        String aux[];
        if(result - resultInt > 0.899) {
            aux = handleGreaterThan089(keys, roman, resultInt, decimalNumberAux);
        }else{
            aux = handleOtherValues(keys, resultInt, roman, decimalNumberAux, holder);
        }
        return aux;
    }
    private String[] handleGreaterThan089(int keys, String roman, int resultInt, Integer decimalNumberAux){
            int auxKey = setAuxKeyValue(keys);
            roman += this.romanValues.get(keys).repeat(resultInt) + this.romanValues.get(auxKey)
                    + this.romanValues.get(keys);
            decimalNumberAux = decimalNumberAux - ((keys * resultInt) + (keys - auxKey));
            return new String[]{roman, Integer.toString(decimalNumberAux)};
    }

    private String[] handleOtherValues(int keys, int resultInt, String roman, int decimalNumberAux, int holder){
        if(resultInt >= 1 && resultInt <= 3){
            roman += this.romanValues.get(keys).repeat(resultInt);
            decimalNumberAux -= (keys * resultInt);
        }else if(resultInt > 3){
            roman += this.romanValues.get(keys) + this.romanValues.get(holder);
            decimalNumberAux -= (holder - keys);
        }
        holder = keys;
        return new String[]{roman, Integer.toString(decimalNumberAux), Integer.toString(holder)};
    }

    private HashMap<Integer, String> fillValues(){
        HashMap<Integer, String> romanValuesAux = new LinkedHashMap<>();
        romanValuesAux.put(1000,"M");
        romanValuesAux.put(500,"D");
        romanValuesAux.put(100,"C");
        romanValuesAux.put(50,"L");
        romanValuesAux.put(10,"X");
        romanValuesAux.put(5,"V");
        romanValuesAux.put(1,"I");
        return romanValuesAux;
    }
}
```
## Code smells Classes grandes
### Caso - 1
A seguir temos um código em kotlin que é responsável por gerar uma senha com base no seu nome. A classe responsável por isso está grande, e pode ser diminuida se fizermos a utilização de algumas ténicas de rafatoração. Exemplo de funcionamento:


<img src="https://i.imgur.com/rFD3ujT.jpg" alt="drawing" height="500" width="300"/>

<br> </br>
Código reponsável por gerar a senha:
```kotlin
  /* Other methods, class etc */
    fun generatePassword(name : String, onPasswordChange : (String) -> Unit){
        val names = cleanSpaces(name).replace("(\\s)+","\\s").split(" ")
        onPasswordChange(contWords(names))

    }

    private fun cleanSpaces(name : String) : String{
        return name.trim()
    }

    private fun contWords(names : List<String>) : String{
        var auxPassword = ""
        for(name in names){
            auxPassword += contLetters(name)
        }
        return auxPassword
    }

    private fun contLetters(name : String) : String {
        return name.length.toString()
    }
```
Para fazer a refatoração destes métodos, eu irei utilizar a técnica de **métodos em linha**, ou seja, os métodos "cleanSpaces" e "contLetters" serão colocados em linha, pois são métodos curtos e não existe motivo aparente para criarmos um método específico para isso. Assim diminuindo o tamanho da nossa classe.

```kotlin
     fun generatePassword(name : String, onPasswordChange : (String) -> Unit){
        val names = name.trim().split(" ")
        onPasswordChange(contWords(names))

    }
    
    private fun contWords(names : List<String>) : String{
        var auxPassword = ""
        for(name in names){
            auxPassword += name.length.toString();
        }
        return auxPassword
    }
    
```
## Code smells - Featury Envy
### Caso - 1
Abaixo temos uma classe "Animal" que possui dois atributos: name e maxSpeed. Além disso temos uma classe "TimeToFinish" e seu método "calcTime" que recebe um "Animal" como parâmetro, e uma distância.
Esse método é responsável por calcular o tempo que um "Animal" precisa para percorrer a distância espeficicada.
```java
public class Animal {

    private String name;
    private Double maxSpeed;

    public Animal(String name, Double maxSpeed){
        this.name = name;
        this.maxSpeed = maxSpeed;
    }

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

    public Double getMaxSpeed() { return maxSpeed; }

    public void setMaxSpeed(Double maxSpeed) { this.maxSpeed = maxSpeed; }


}

public class TimeToFinish {
    public String calcTime(Animal animal, double kilometers){
        double animalMaxSpeed = animal.getMaxSpeed();
        double time = kilometers / animalMaxSpeed;
        return String.format("The %s can run %.2f kilometers in %.2f hours",
                animal.getName(), kilometers, time);

    }
}

```

Vejamos que o método calcTime "inveja" muitos métodos da classe "Animal", logo podemos usar a **extração de métodos**, e colocarmos este método calcTime na própria classe "Animal".

```java
public class Animal {

    private String name;
    private Double maxSpeed;

    public Animal(String name, Double maxSpeed){
        this.name = name;
        this.maxSpeed = maxSpeed;
    }

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

    public Double getMaxSpeed() { return maxSpeed; }

    public void setMaxSpeed(Double maxSpeed) { this.maxSpeed = maxSpeed; }

    public String calcTime(double kilometers){
        double animalMaxSpeed = this.getMaxSpeed();
        double time = kilometers / animalMaxSpeed;
        return String.format("The %s can run %.2f kilometers in %.2f hours",
                this.getName(), kilometers, time);

    }


}
```

## Code smells - Métodos com muitos parâmetros
### Caso - 1
A seguir vemos a classe "ClientServiceImpl" que implementa "ClientService", porém o método sobrescrito, chama atenção pelo número de parâmetros, ter muitos parâmetros em um método geralmente é ruim, ainda mais neste caso onde podemos facilmente encapsula-los em um classe "Client".

```java
public class ClientServiceImpl implements  ClientService{
    
    private ClientDAO dao;

    @Override
    public int createClient(String name, String email, String password, int age, String phone, String street, String number) {
        return dao.insertClient(name, email, password, age, phone, street, number);
    }
}
```
Neste caso faremos uma **mudança de assinatura dos métodos**, tanto o método "createClient", quanto o método "insertClient" da classe "ClientDao" terão que ser mudados. 

```java
public class ClientServiceImpl implements  ClientService{
    
    private ClientDAO dao;
    
    @Override
    public int createClient(Client client) {
        return dao.insertClient(client);
    }
}
```
## Code smells - Variáveis globais
### Caso - 1
Vemos no programa abaixo, pelo método "incrementaImpar" a utilização de uma variável global. Isso raramente é usado, pois isto viola o conceito de transparência referêncial, tendo em vista que precisamos de algo externo ao método para entendermos sua saída.

```java
public class SomaImpar {

    private static int valor;

    public static void main(String[] args) {
        valor = 25;
        int[] vetor = {1,2,3,4,5,6};
        incrementaImpar(vetor);
        printVetor(vetor);


    }

    public static void incrementaImpar(int [] vetor){
        for(int i = 0; i < vetor.length; i++){
            if(vetor[i] % 2 != 0) vetor[i] += valor;
        }
    }

    public static void printVetor(int []vetor){
        for(int i = 0; i < vetor.length; i++){
            System.out.print(vetor[i]+" ");
        }
    }
}
```

Aqui excluiremos a variável global, e ao invés disso utilizaremos um novo parâmetro no método "incrementaImpar".

```java
public class SomaImpar {
    
    public static void main(String[] args) {
        int[] vetor = {1,2,3,4,5,6};
        incrementaImpar(vetor, 25);
        printVetor(vetor);


    }

    public static void incrementaImpar(int [] vetor, int valor){
        for(int i = 0; i < vetor.length; i++){
            if(vetor[i] % 2 != 0) vetor[i] += valor;
        }
    }

    public static void printVetor(int []vetor){
        for(int i = 0; i < vetor.length; i++){
            System.out.print(vetor[i]+" ");
        }
    }
}
```
### Caso - 2
O projeto abaixo contém a classe Product, e cada produto possui nome e valor, porém queremos adicionar um desconto a todos os produtos, e fazemos isto por meio de uma variável global.

```java
public class Discont {

    private static Double valueOfDiscont = 0.15;

    public static void main(String[] args) {

        List<Product> listOfProducts = new ArrayList<>();
        listOfProducts.add(new Product("TV",1002.25));
        listOfProducts.add(new Product("Mouse",256.0));
        listOfProducts.add(new Product("Notebook",4578.56));
        applyDiscont(listOfProducts);


    }

    public static void applyDiscont(List<Product> listOfProducts){
        for(Product product : listOfProducts){
            product.setPrice(product.getPrice() - product.getPrice() * valueOfDiscont);
            System.out.println(product.toString());
        }
    }
}

```

Para diferenciarmos um pouco, ao invés de fazermos a mesma coisa, desta vez vamos utilizar **programação funcional** para refatorar esta classe.
```java
public class Discont {

    public static void main(String[] args) {
        List<Product> listOfProducts = new ArrayList<>();
        listOfProducts.add(new Product("TV",1002.25));
        listOfProducts.add(new Product("Mouse",256.0));
        listOfProducts.add(new Product("Notebook",4578.56));
        double discont = 0.15;
        listOfProducts.stream().forEach((product) ->   product.setPrice(product.getPrice() - product.getPrice() * discont));
        listOfProducts.forEach((product -> System.out.println(product)));
    }


}

```
## Code smells - Obsessão por tipos primitivos
### Caso - 1
Na parte sobre métodos com muitos parâmetros, vimos que poderiamos encapsular estes parâmetros, passando o próprio client no método. Agora vamos ver como esta implementada esta classe "Client".
```java
public class Client {
    private String name;
    private String email;
    private String password;
    private int age;
    private String phone;
    private String street;
    private String number;

    public Client(String name, String email, String password, int age, String phone, String street, String number) {
        this.name = name;
        this.email = email;
        this.password = password;
        this.age = age;
        this.phone = phone;
        this.street = street;
        this.number = number;
    }
    // getters and setters, toString() etc
}
```
Esta classe "Client", tem o que chamamos de obsessão por tipos primitivos, ou seja, tem muitos atributos de tipos primitivos sem necessidade. Para fazer este "Refactoring" vamos criar três  novas classes: "Address", "PhoneNumber" e "Account" 

```java
public class Client {

    private String name;
    private int age;
    private PhoneNumber phone;
    private Account account;
    private Address address;

    public Client(String name, int age, PhoneNumber phone, Account account, Address address) {
        this.name = name;
        this.age = age;
        this.phone = phone;
        this.account = account;
        this.address = address;
    }
    
    // getters setters toString
}
public class Account {
    
    private String email;
    private String password;

    public Account(String email, String password) {
        this.email = email;
        this.password = password;
    }
    // getters setters toString
}

public class Address {

    private String street;
    private String homeNumber;

    public Address(String street, String homeNumber) {
        this.street = street;
        this.homeNumber = homeNumber;
    }
    // getters setters toString
}

public class PhoneNumber {
    private String phoneNumber;

    public PhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
    // getters setters toString()
}

```

## Code smells - Objetos mutáveis
### Caso - 1
Objetos mútaveis, podem ser um grande problema nas nossas aplicações, pois este objeto pode ter seu valor alterado indesejavelmente, logo sempre que pudermos deveremos optar pelo uso de objetos imutáveis. A classe "Dog" abaixo, não respeita este conceito.
```java
public class Dog {

    private String name;
    private String  breed;
    private int age;

    public Dog(String name, String breed, int age) {
        this.name = name;
        this.breed = breed;
        this.age = age;
    }
    
    // getters and setters
}

```
Neste caso, os atributos name e breed estão ok, pois são Strings e no java Strings são imutáveis,  contudo o atributo age não é, por isso iremos refatora-lo. Agora ele sera "final", ou seja, o valor de age não poderá ser mudado.

```java
public class Dog {

    private String name;
    private String  breed;
    private final int age;

    public Dog(String name, String breed, int age) {
        this.name = name;
        this.breed = breed;
        this.age = age;
    }

    // getters and setters
}
```
Obs: Caso a classe "Dog", não fosse possuir nenhuma subclasse, poderiamos também deixar a classe "final".



## Code smells - Comentários
### Caso - 1
A seguir temos um código onde eu fiz alguns
comentários inúteis.
```java
    //Buttons

    @FXML
    Button btEquals;

    @FXML
    Button btAc;
    
    @FXML
    Button btDelete;
    
    //TextField
    
    @FXML
    TextField tfResult;
	
```
O "refactoring" deste é bem simples, apenas fiz a **remção dos comentários**.

 ```java

    @FXML
    Button btEquals;

    @FXML
    Button btAc;
    
    @FXML
    Button btDelete;
    
    @FXML
    TextField tfResult;
	
```
	

### Caso - 2

Neste caso temos um método comentado, este método é responsável por verificar se o número esta no padrão correto, porém este comentário não serve para nada, já que fica explícito na assinatura do método, o que ele faz.

```java
    // verifica se o número esta dentro do padrão
   public static Boolean veirfyIsValid(String phoneNumber){
        String pattern = "\\(([0-9]{3})\\)\\s([0-9]{3})-([0-9]{4})";
        return phoneNumber.matches(pattern);
    }
```
Para fazer o "refactoring", eu **retirei este comentário ruído e adicionei um comentário útil**.

```java
    // pattern = (111) 255-9867
   public static Boolean veirfyIsValid(String phoneNumber){
        String pattern = "\\(([0-9]{3})\\)\\s([0-9]{3})-([0-9]{4})";
        return phoneNumber.matches(pattern);
    }
```




## Code smells - Métodos acidentalmente complexos
### Caso - 1
No caso abaixo, temos um método cujo o objetivo é transformar os segundos recebidos como parâmetro, no formato de dias horas minutos e segundos.
Exemplo: se a entrada fosse 618919 segundos a saída seria: "7 days, 3 hours, 55 minutes and 19 seconds". Porém esse método está extremamente complexo, por isso iremos fazer a refatoração do mesmo.

```java
    public static String format(int seconds){
        String userMessage = "";
        if(seconds <= 0) return "now";
        else {
            HashMap<Integer, String> timeInstances = new LinkedHashMap<>();
            timeInstances.put(31536000,"years");
            timeInstances.put(86400,"days");
            timeInstances.put(3600,"hours");
            timeInstances.put(60,"minutes");

            int cont = timeInstances.size() - 1;

            for(Integer keys : timeInstances.keySet()) {
                double convertedSeconds = seconds / keys;
                if (convertedSeconds >= 1) {
                    String timeUnit = timeInstances.get(keys);
                    int auxValue = (Math.round((int) convertedSeconds));
                    String formatedTimeUnit = auxValue != 1 ? timeUnit : timeUnit.substring(0, timeUnit.length() - 1);
                    userMessage +=  auxValue + " " + formatedTimeUnit + ", ";
                    seconds = seconds % keys;
                }
            }

            if(userMessage != ""){
                if(userMessage.charAt(userMessage.length() - 1) == ' ')
                    userMessage = userMessage.substring(0, userMessage.length() - 2) + " ";
            }

            if(seconds != 0){
                seconds = Math.round(seconds);
                String timeUnit = seconds > 1 ? "seconds" : "second";
                if(userMessage.equals("")) userMessage += seconds + " " + timeUnit;
                else userMessage += "and " + seconds + " " + timeUnit;

            }else if(userMessage.length() > 10){
                int indexOfLastComma = userMessage.lastIndexOf(',');
                userMessage = userMessage.substring(0, indexOfLastComma) + " and" + userMessage.substring(indexOfLastComma + 1, userMessage.length());
            }
        }

        return userMessage.trim();
    }
```

Aqui faremos uma **extração de método**, além disso iremos **simplificar a lógica do método.**
```java
    public static String format(int seconds){
        if(seconds == 0) return "now";
        String [] formatedTimes = fillFormatedValues(seconds);
        return Arrays.stream(formatedTimes)
            .filter(time -> time != "")
            .collect(Collectors.joining(", "))
            .replaceAll(", (?!.+,)", " and ");
      
    }
    
    public static String[] fillFormatedValues(int seconds){
        String[] formatedTimes = new String[]{
                formatTime("year", (seconds / 31536000)),
                formatTime("day", (seconds / 86400) % 365),
                formatTime("hour", (seconds / 3600) % 24),
                formatTime("minute", (seconds / 60) % 60),
                formatTime("second", (seconds % 3600) % 60)
        };
        return formatedTimes;
    }
    public static String formatTime(String s, int time){
            return time==0 ? "" : Integer.toString(time)+ " " + s + (time==1?"" : "s");
    }

```
### Caso - 2
Na situação abaixo, assim como na de cima, temos um método difícil de entender, apesar desta situação ser bem melhor do que a anterior, ainda sim, podemos fazer o refactoring neste caso também. Este método é responsável por inverter as palavras de uma frase recebida como parâmetro, deste que a mesma tenha 5 ou mais letras. Exemplo: "Hello World", "olleH dlroW".
```java
    public String spinWords(String sentence) {
        String[] splittedWords = sentence.split(" ");
        StringBuilder builder = new StringBuilder();

        for (String word : splittedWords) {
            if (word.length() > 4) {
                StringBuilder tempBuilder = new StringBuilder();
                tempBuilder.append(word).reverse();
                builder.append(tempBuilder).append(" ");
            } else {
                builder.append(word).append(" ");
            }
        }
        builder.deleteCharAt(builder.length() - 1);
        return builder.toString();
    }
```
Utilizando a **extração de métodos** e **mudando um pouco a lógica** usada temos :
```java
  public String spinWords(String sequence) {
    List<String> words = List.of(sequence.split(" "));
    List<String> reversedWords = words.stream().map(SpinWords::reverse).collect(Collectors.toList());
    return String.join(" ",reversedWords);
  }
  
  public static String reverse(String word){
      if(word.length() >= 5)
          return new StringBuilder(word).reverse().toString();
      return word;
  }
```



## Débito técnico - Nomes fora do padrão
Não é novidade para ninguém que, fazer um código que um computador entenda é muito mais fácil do que fazer um código que outro programador entenda. Tendo isto em vista, nós programadores seguimos algumas convenções de nomes de classes, pacotes e variáveis. A seguir mostrarei o refactoring de nomes de classes fora do padrão.

### Caso - 1
Na imagem abaixo, podemos ver que todos os arquivos .fxml estão seguindo um padrão de camelCase, porém um deles está no padrão de snake_case. Isto contudo não é uma boa prática, logo este arquivo é um canditado a refatoração.


<img src="https://i.imgur.com/Ygxba4D.png" alt="drawing" width="700"/> 

<br></br>

Temos duas opções aqui, a padrão que seria refatorar o arquivo para "tableContentView.fxml", e a outra trocar todos os outros para o formato snake_case, como em um projeto de grande porte a segunda opção seria inviável, faremos a primeira mesmo.

![](https://i.imgur.com/xTFr9V5.png)



### Caso - 2
Seguindo o mesmo projeto anterior, o nome das classes controller também estão em desordem, tendo em vista que algumas estão com o padrão: "nome + View + Controller", e outras estão com "nome + Controller". Exemplos: CreateAccountController e MessageViewController. Contudo vamos refatorar e iremos padronizar todos os nomes de classes que são "controladoras".
![](https://i.imgur.com/aqz1ENe.png)

## Débito Técnico - Classes não testadas
### Caso - 1
O código a seguir já foi visto anteriormente, porém mesmo após o refactoring do mesmo ainda precisamos testa-lo.

```java
public class RomanNumeralsEncoder {
    private final HashMap<Integer, String> romanValues = fillValues();
    private Integer decimalNumber;

    public RomanNumeralsEncoder(int number){
        this.decimalNumber = number;
    }

    public String toRoman() {
        String roman = "";
        int holder = 0;
        Integer decimalNumberAux = this.decimalNumber;

        for (Integer keys : this.romanValues.keySet()) {
            double result = (double) decimalNumberAux / keys;
            int resultInt = (int) result;

            String [] values = handleResult(result, resultInt, roman, decimalNumberAux, keys, holder);
            roman = values[0];
            decimalNumberAux = Integer.parseInt(values[1]);
            holder = values.length > 2 ? Integer.parseInt(values[2]) : holder;

        }
        return roman;
    }

    private int setAuxKeyValue(int keys){
        int auxKey = 0;

        if (keys == 5 || keys == 50 || keys == 500) {
            auxKey = keys - (keys - (keys / 5));
        } else {
            auxKey = keys - (keys - (keys / 10));
        }

        return auxKey;
    }

    private String[] handleResult(double result, int resultInt, String roman, int decimalNumberAux, int keys, int holder){
        String aux[];
        if(result - resultInt > 0.899) {
            aux = handleGreaterThan089(keys, roman, resultInt, decimalNumberAux);
        }else{
            aux = handleOtherValues(keys, resultInt, roman, decimalNumberAux, holder);
        }
        return aux;
    }
    private String[] handleGreaterThan089(int keys, String roman, int resultInt, Integer decimalNumberAux){
            int auxKey = setAuxKeyValue(keys);
            roman += this.romanValues.get(keys).repeat(resultInt) + this.romanValues.get(auxKey)
                    + this.romanValues.get(keys);
            decimalNumberAux = decimalNumberAux - ((keys * resultInt) + (keys - auxKey));
            return new String[]{roman, Integer.toString(decimalNumberAux)};
    }

    private String[] handleOtherValues(int keys, int resultInt, String roman, int decimalNumberAux, int holder){
        if(resultInt >= 1 && resultInt <= 3){
            roman += this.romanValues.get(keys).repeat(resultInt);
            decimalNumberAux -= (keys * resultInt);
        }else if(resultInt > 3){
            roman += this.romanValues.get(keys) + this.romanValues.get(holder);
            decimalNumberAux -= (holder - keys);
        }
        holder = keys;
        return new String[]{roman, Integer.toString(decimalNumberAux), Integer.toString(holder)};
    }

    private HashMap<Integer, String> fillValues(){
        HashMap<Integer, String> romanValuesAux = new LinkedHashMap<>();
        romanValuesAux.put(1000,"M");
        romanValuesAux.put(500,"D");
        romanValuesAux.put(100,"C");
        romanValuesAux.put(50,"L");
        romanValuesAux.put(10,"X");
        romanValuesAux.put(5,"V");
        romanValuesAux.put(1,"I");
        return romanValuesAux;
    }
}
```


Para fazermos isto, eu criei uma classe de teste, e fiz alguns testes para previnir futuros "bugs".
```java
public class RomanNumeralsEncoderTest {

    public static RomanNumeralsEncoder encoder;

    @BeforeClass
   public static void setUp() {
       encoder = new RomanNumeralsEncoder();
    }

   @Test
   public void when9MustBeEqualsToIX(){
       encoder.setDecimalNumber(9);
       String romanNumeral = encoder.toRoman();
       assertEquals("IX",romanNumeral);
   }

    @Test
    public void when40MustBeEqualsToXL(){
        encoder.setDecimalNumber(40);
        String romanNumeral = encoder.toRoman();
        assertEquals("XL",romanNumeral);
    }

    @Test
    public void when3865MustBeEqualsToMMMDCCCLXV(){
        encoder.setDecimalNumber(3865);
        String romanNumeral = encoder.toRoman();
        assertEquals("MMMDCCCLXV",romanNumeral);
    }

}
```
### Caso - 2
Outra classe que já vimos antes.

```java
public static String format(int seconds){
        if(seconds == 0) return "now";
        String [] formatedTimes = fillFormatedValues(seconds);
        return Arrays.stream(formatedTimes).filter(time -> time != "").collect(Collectors.joining(", ")).replaceAll(", (?!.+,)", " and ");
    }

    public static String[] fillFormatedValues(int seconds){
        String[] formatedTimes = new String[]{
                formatTime("year", (seconds / 31536000)),
                formatTime("day", (seconds / 86400) % 365),
                formatTime("hour", (seconds / 3600) % 24),
                formatTime("minute", (seconds / 60) % 60),
                formatTime("second", (seconds % 3600) % 60)
        };
        return formatedTimes;
    }
    public static String formatTime(String s, int time){
        return time==0 ? "" : Integer.toString(time)+ " " + s + (time==1?"" : "s");
    }
```

Mas agora iremos testa-la.

```java
public class TimeFormmaterTest {

    @Test
    public void when0SholdBeNow(){
        Assert.assertEquals("now", TimeFormmater.format(0));
    }

    @Test
    public void when1ShouldBe1second(){
        Assert.assertEquals("1 second", TimeFormmater.format(1));
    }

    @Test
    public void when2ShouldBe2seconds(){
        Assert.assertEquals("2 seconds", TimeFormmater.format(2));
    }

    @Test
    public void when152ShouldBe2minutesand32seconds(){
        Assert.assertEquals("2 minutes and 32 seconds", TimeFormmater.format(152));
    }
}
```




