% Intro Refactoring
\newpage{}
Source: http://kfaday.blogspot.com/2016/01/la-vida-de-un-ingeniero-de-sistemas.html
But…
Legacy code is code without tests.
Code without testing is bad code. It doesn’t matter how well written it is; no matter how pretty it is either object-oriented or well encapsulated. With testing, we can change the behavior of our code quickly and verifiably. Without them, we don’t know if our code is getting better or worse.
(2004) M. Feather
Behavior is the most important thing in the software. It’s what users depend on. Users like it when we add features (as long as it is what they wanted), but if we change or remove the behavior they depend on (we introduce bugs), they stop trusting us.
The act of improving the design without changing its behavior is called refactoring.
Refactoring is focused on the structure of the program.
Optimization focuses on the use of resources.
Adding a Feature | Fixing a Bug | Refactoring | Optimizing | |
---|---|---|---|---|
Structure | Changes | Changes | Changes | - |
Functionality | Changes | Changes | - | - |
Resource Usage | - | - | - | Changes |
Preserving existing behavior is one of the biggest challenges in software development. Even when we are changing the basic characteristics, we often have very large areas of behavior that we must preserve.
Industry-standard?
Tests
When we have tests that detect changes, it is like having a vision of our code. Code behavior is fixed in place. When we make changes, we can know that we are changing only one part of the behavior at a time. In short, we are in control of our work.
Dependencies are one of the most critical problems in software development. Much of the work with legacy code involves breaking dependencies to make the switch easier.
When we change the code, we should have tests in place. For testing, we often have to change the code.
When you break dependencies in legacy code, you often have to suspend your sense of aesthetics a bit. Some dependencies break cleanly; Others end up looking less ideal from a design point of view. They are like incision points in surgery - a scar may be left on your code after your work, but everything underneath may improve.
“I do not know.”
You look around you. Is there testing in place? No.
We ask, “How badly do they need it?”
We know that we can make changes to a few lines in 10 places, and it would be until 5:00 p.m. Is it an emergency? Can it be fixed tomorrow? The code is our house, and we have to live in it.
public class TransactionGate {
public void postEntries(List entries) {
for (Iterator it = entries.iterator(); it.hasNext(); ) {
Entry entry = (Entry)it.next();
entry.postDate();
}
transactionBundle.getListManager().add(entries);
}
//...
}
Let’s add a feature: Verify new entries are not previously in transactionBundle
It could be implemented “easily” with:
public class TransactionGate {
public void postEntries(List entries) {
List entriesToAdd = new LinkedList();
for (Iterator it = entries.iterator(); it.hasNext(); ) {
Entry entry = (Entry)it.next();
if (!transactionBundle.getListManager().hasEntry(entry) {
entry.postDate();
entriesToAdd.add(entry);
}
}
transactionBundle.getListManager().add(entriesToAdd);
}
//...
}
But… we fall back into the legacy code loop. Let’s now try the Sprout method.
Let’s extract the logic to the method.
public class TransactionGate {
//...
List uniqueEntries(List entries) {
List result = new ArrayList();
for (Iterator it = entries.iterator(); it.hasNext(); ) {
Entry entry = (Entry)it.next();
if (!transactionBundle.getListManager().hasEntry(entry) {
result.add(entry);
}
}
return result;
}
//...
}
public class TransactionGate{
//...
public void postEntries(List entries) {
List entriesToAdd = uniqueEntries(entries);
for (Iterator it = entriesToAdd.iterator(); it.hasNext(); ) {
Entry entry = (Entry)it.next();
entry.postDate();
}
transactionBundle.getListManager().add(entriesToAdd);
}
//...
}
std::string QuarterlyReportGenerator::generate() {
std::vector<Result> results = database.queryResults(
beginDate, endDate);
std::string pageText;
pageText += "<html><head><title>"
"Quarterly Report"
"</title></head><body><table>";
if (results.size() != 0) {
for (std::vector<Result>::iterator it = results.begin();it != results.end();++it) {
pageText += "<tr>";
pageText += "<td>" + it->department + "</td>";
pageText += "<td>" + it->manager + "</td>";
char buffer [128];
sprintf(buffer, "<td>$%d</td>", it->netProfit / 100);
pageText += std::string(buffer);
sprintf(buffer, "<td>$%d</td>", it->operatingExpense / 100);
pageText += std::string(buffer);
pageText += "</tr>";
}
} else {
pageText += "No results for this period";
}
pageText += "</table>";
pageText += "</body>";
pageText += "</html>";
return pageText;
}
Let’s add add a feature: Add a header to the type list
<tr><td>Department</td><td>Manager</td><td>Profit</td><td>Expenses</td></tr>
Let’s extract the logic to class
using namespace std;
class QuarterlyReportTableHeaderProducer{
public:
string makeHeader();
};
string QuarterlyReportTableProducer::makeHeader(){
return "<tr><td>Department</td><td>Manager</td>"
"<td>Profit</td><td>Expenses</td>";
}
We add invocation to the original method
QuarterlyReportGenerator::generate():
//...
QuarterlyReportTableHeaderProducer producer;
pageText += producer.makeHeader();
//...
Are you serious? A method for this
Yes!
It allows additional refactoring
With an interface:
class QuarterlyReportTableHeaderGenerator{
public:
string generate();
};
We can have different implementations:
class HTMLGenerator{
public:
virtual ~HTMLGenerator() = 0;
virtual string generate() = 0;
};
class QuarterlyReportTableHeaderGenerator : public HTMLGenerator {
public:
//...
virtual string generate();
//...
};
class QuarterlyReportGenerator : public HTMLGenerator {
public:
//...
virtual string generate();
//...
};
Public Class ManagerProdFin
{
Public Sub BuscarAltas(ByVal fechadesde As Date, ByVal fechahasta As Date, ByVal solovigentes As Boolean)
{
'...
Dim listaapoderadosaltas = From apo As Apoderados In ListaApoderados Where
Convert.ToInt32(apo.aponrocuenta) = Convert.ToInt32(obj.NroCliente)
AndAlso Not apo.apotipodocumento.Equals("UBO")
AndAlso (Not solovigentes OrElse
Not apo.apoFechaVencimiento.HasValue()
OrElse apo.apoFechaVencimiento.Value.CompareTo(fechahasta) > 0) Select apo
'...
}
}
Public Class ManagerProdFin
{
Public Sub BuscarAltas(ByVal fechadesde As Date, ByVal fechahasta As Date, ByVal solovigentes As Boolean)
{
'...
Dim listatitularesaltas As List(Of Titulares) = New List(Of Titulares)
listatitularesaltas = AuxManager.titularesvinculados(obj, ListaTitulares)
'...
}
}
Public Class ManagerAuxiliar
Public Sub New()
End Sub
Public Function titularesvinculados(ByVal ObjCustomer As Customer_Account,
ByVal archivotitulares As List(Of Titulares)) As List(Of Titulares)
Dim archivofiltrado = New List(Of Titulares)
Dim listafiltrada = From titu As Titulares In archivotitulares
Where Convert.ToInt32(titu.titubase_no) = ObjCustomer.NroClienteAsInt() Select titu
For Each T In listafiltrada
archivofiltrado.Add(T)
Next
Return archivofiltrado
End Function
End Class
Imports NUnit.Framework
<TestFixture()>
Public Class ManagerAuxiliarUnitTest
<Test()>
Public Sub GivenObjCustandtitulares_WhenTvinculados_ThenArchfiltrado()
Dim AuxiliaryM As ManagerAuxiliar = New ManagerAuxiliar()
Dim titularesSet As List(Of Titulares) = New List(Of Titulares)
titularesSet.Add(New Titulares With {.titubase_no = "0000123"})
titularesSet.Add(New Titulares With {.titubase_no = "0000124"})
titularesSet.Add(New Titulares With {.titubase_no = "0000125"})
Dim Customer As Customer_Account = New Customer_Account()
Customer.campocustacc = "000124"
Dim result As List(Of Titulares) = AuxiliaryM.titularesvinculados(Customer, titularesSet)
Assert.That(result, Has.Count().EqualTo(1))
End Sub
End Class
public class Employee{
//...
public void pay() {
Money amount = new Money();
for (Iterator it = timecards.iterator(); it.hasNext(); ) {
Timecard card = (Timecard)it.next();
if (payPeriod.contains(date)) {
amount.add(card.getHours() * payRate);
}
}
payDispatcher.pay(this, date, amount);
}
}
Update a file every time a payment is run to employees
We can add the functionality at the end of the method, but what if …
public class Employee {
private void dispatchPayment() { // <1>
Money amount = new Money();
for (Iterator it = timecards.iterator(); it.hasNext(); ) {
Timecard card = (Timecard)it.next();
if (payPeriod.contains(date)) {
amount.add(card.getHours() * payRate);
}
}
payDispatcher.pay(this, date, amount);
}
public void pay() { // <2>
logPayment(); // <3>
dispatchPayment(); // <4>
}
private void logPayment() {
//...
}
}
public class Employee {
public void makeLoggedPayment() {
logPayment();
pay();
}
public void pay() {
//...
}
private void logPayment() {
//...
}
}
There are only two hard things in Computer Science: cache invalidation and naming things. - Phil Karlton.
class LoggingEmployee extends Employee {
public LoggingEmployee(Employee e) {
employee = e;
}
public void pay() {
logPayment();
employee.pay();
}
private void logPayment() {
//...
}
//...
}
This technique uses the decorator pattern. We create objects of a class that wrap another class and pass them. The class you are wrapping must have the same interface as the class you are wrapping so that clients don’t know they are working with a wrapper. In the example, LoggingEmployee
is a decorator for Employee
. You must have a pay ()
method and any other clerk methods that the client uses.
class LoggingPayDispatcher {
private Employee e;
public LoggingPayDispatcher(Employee e) {
this.e = e;
}
public void pay() {
employee.pay();
logPayment();
}
private void logPayment() {
//...
}
//...
}
The key to the Wrap Class is that new behavior can be added to a system without adding it to an existing class. When there are many calls to the code that you want to wrap, it is often worth moving to a wrapper that uses the decorator pattern.
When you use the decorator pattern, a new behavior can be transparently added to an existing call set, such as pay()
, all at the same time.
Yes, see catalog of refactorings: https://refactoring.com/catalog/
** Working Effectively with Legacy Code, 1st. Michael Feathers (2004)
** http://xurxodev.com/trabajando-con-codigo-legado/
** http://xurxodev.com/trabajando-con-codigo-legado-sprout-method-y-sprout-class/
** https://www.yegor256.com/2016/01/26/defensive-programming.html
I help developers to deliver good quality software following the best practices and applying clean code principles - Christian - Husband