Legacy code

Refactoring

(по мотивам мастер-класса на ag;)e days 2014)



План


  1. Подмена понятий (Extract and Override Factory Method)
  2. Dependency Injection method
  3. Почкование (Росток, Sprout method)
  4. Охват (Wrap method)

Общий Подход:


  • Находим зависимость от технологий
  • Изолируем каким-либо способом
  • Пишем тест

Extract and Override


package com.smartstepgroup.awfulvideostore;

import java.io.IOException;

public class LoginService {
    public void login(String userName, String password) throws IOException {
        if (userName == "admin" && password == "admin") {
            Session.add("LoggedUser", "admin");
        }
        if (userName == "user" && password == "user")
        {
            Session.add("LoggedUser", "user");
        }
    }

} 

Extract and Override


package com.smartstepgroup.awfulvideostore;
import java.io.IOException;

public class LoginService {
  public void login(String userName, String password) throws IOException {
    if (userName == "admin" && password == "admin") {
    AddLoggedUserToSession("admin");
}
if (userName == "user" && password == "user"){ AddLoggedUserToSession("user");
}
} protected void AddLoggedUserToSession(String username) throws IOException { Session.add("LoggedUser", username); } }

Dependency Injection


package com.smartstepgroup.awfulvideostore;

import javax.xml.stream.XMLStreamException;
import java.io.FileNotFoundException;
import java.util.ArrayList;

public class DefaultMovieService {
public Movie[] load() throws Exception {
Movie[] finalResult = new Movie[0];
if (!Session.hasKey("LoggedUser")) {
throw new Exception("User is not authorized");
}

if (Session.hasValue("LoggedUser", "admin")) {
XmlDocument xmlMovies = loadXml();
ArrayList<Movie> resultMovies = new ArrayList<Movie>();
for (int i = 0; i < xmlMovies.Elements.size(); i++) {
Movie movie = new Movie(Integer.parseInt(xmlMovies.Elements.get(i).Values[3]));
movie.Rating = Integer.parseInt(xmlMovies.Elements.get(i).Values[2]);
movie.Title = xmlMovies.Elements.get(i).Values[0];
movie.Price = xmlMovies.Elements.get(i).Values[1];
resultMovies.add(movie);

}
Movie[] result = new Movie[resultMovies.size()];
finalResult = resultMovies.toArray(result);
}
else {
if (Session.hasValue("LoggedUser", "user")) {
XmlDocument xmlMovies = loadXml();
ArrayList<Movie> resultMovies = new ArrayList<Movie>();

for (int i = 0; i < xmlMovies.Elements.size(); i++) {
if (Integer.parseInt(xmlMovies.Elements.get(i).Values[2]) <= 14)
{
Movie movie = new Movie(Integer.parseInt(xmlMovies.Elements.get(i).Values[3]));
movie.Rating = Integer.parseInt(xmlMovies.Elements.get(i).Values[2]);
movie.Title = xmlMovies.Elements.get(i).Values[0];
movie.Price = xmlMovies.Elements.get(i).Values[1];
resultMovies.add(movie);
}
}
Movie[] result = new Movie[resultMovies.size()];
finalResult = resultMovies.toArray(result);
}
}

return finalResult;
}

private XmlDocument loadXml() throws FileNotFoundException, XMLStreamException {
XmlDocument document = new XmlDocument();
document.load();
return document;
}
}



Dependency Injection

package com.smartstepgroup.awfulvideostore;

import java.io.IOException;

public interface ISession {

public boolean hasKey(String loggedUser) throws IOException;
public boolean hasValue(String key, String value) throws IOException;

}

package com.smartstepgroup.awfulvideostore;

import javax.xml.stream.XMLStreamException;
import java.io.FileNotFoundException;
import java.util.ArrayList;

public class DefaultMovieService {

private ISession session;

public DefaultMovieService(ISession sess) {
this.session = sess;
}


public Movie[] load() throws Exception {
Movie[] finalResult = new Movie[0];
if (!session.hasKey("LoggedUser")) {
throw new Exception("User is not authorized");
}

if (session.hasValue("LoggedUser", "admin")) {
XmlDocument xmlMovies = loadXml();
ArrayList<Movie> resultMovies = new ArrayList<Movie>();
for (int i = 0; i < xmlMovies.Elements.size(); i++) {
Movie movie = new Movie(Integer.parseInt(xmlMovies.Elements.get(i).Values[3]));
movie.Rating = Integer.parseInt(xmlMovies.Elements.get(i).Values[2]);
movie.Title = xmlMovies.Elements.get(i).Values[0];
movie.Price = xmlMovies.Elements.get(i).Values[1];
resultMovies.add(movie);

}
Movie[] result = new Movie[resultMovies.size()];
finalResult = resultMovies.toArray(result);
}
else {
if (session.hasValue("LoggedUser", "user")) {
XmlDocument xmlMovies = loadXml();
ArrayList<Movie> resultMovies = new ArrayList<Movie>();

for (int i = 0; i < xmlMovies.Elements.size(); i++) {
boolean rating = Integer.parseInt(xmlMovies.Elements.get(i).Values[2]) <= 14;

Movie movie = new Movie(Integer.parseInt(xmlMovies.Elements.get(i).Values[3]));
movie.Rating = Integer.parseInt(xmlMovies.Elements.get(i).Values[2]);
movie.Title = xmlMovies.Elements.get(i).Values[0];
movie.Price = xmlMovies.Elements.get(i).Values[1];

if (rating || isNewRelease(movie))
{
resultMovies.add(movie);
}
}
Movie[] result = new Movie[resultMovies.size()];
finalResult = resultMovies.toArray(result);
}
}

return finalResult;
}

private boolean isNewRelease(Movie movies) {
return false;
}

private XmlDocument loadXml() throws FileNotFoundException, XMLStreamException {
XmlDocument document = new XmlDocument();
document.load();
return document;
}
}


Sprout method


public void AppendFilesWithText(IList<string> filePath, string textToAppend)
{
filePath = filePath.Select(x => x).Distinct();
foreach (var file in filePath)
{
if (File.Exists(file) && string.IsNullOrEmpty(textToAppend))
File.AppendAllText(file, textToAppend);
}
NotifyThatFilesHaveChangedByEmail(filePath);
}

Sprout method


public void AppendFilesWithText(IList<string> filePath, string textToAppend)
{
filePath = GetUniqueFilePath(filePath);
foreach (var file in filePath)
{
if (File.Exists(file) && string.IsNullOrEmpty(textToAppend))
File.AppendAllText(file, textToAppend);
}
NotifyThatFilesHaveChangedByEmail(filePath);
}

public IList<string> GetUniqueFilePath(IList<string> filePath)
{
filePath = filePath.Select(x => x).Distinct();
return filePath;
}


Wrap method

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

Wrap method

public class Employee
{
    private void dispatchPayment() {
        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() {

        logPayment();
        dispatchPayment();

    }


    private void logPayment() {
    ...
    }

}

Links



  1. https://github.com/SmartStepGroup/AwfulVideoStoreJava
  2. http://www.slideshare.net/SmartStepGroup/ss-32655053
  3. http://taswar.zeytinsoft.com/2009/08/31/learn-the-sprout-method-for-adding-new-functionality/
  4. http://danlimerick.wordpress.com/2012/04/25/tdd-when-up-to-your-neck-in-legacy-code/
  5. https://vimeo.com/25505404
  6. https://vimeo.com/25520076
  7. https://www.dropbox.com/s/jxj89dsxu6sb226/legacy%20code%20-%202009.djvu


Testing

By Sergey Lobin

Testing

  • 357