# 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.

### 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".

## 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));
}
}
```