Legacy code
Refactoring
(по мотивам мастер-класса на ag;)e days 2014)
План
- Подмена понятий (Extract and Override Factory Method)
- Dependency Injection method
- Почкование (Росток, Sprout method)
- Охват (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
- https://github.com/SmartStepGroup/AwfulVideoStoreJava
-
http://www.slideshare.net/SmartStepGroup/ss-32655053
- http://taswar.zeytinsoft.com/2009/08/31/learn-the-sprout-method-for-adding-new-functionality/
-
http://danlimerick.wordpress.com/2012/04/25/tdd-when-up-to-your-neck-in-legacy-code/
- https://vimeo.com/25505404
- https://vimeo.com/25520076
-
https://www.dropbox.com/s/jxj89dsxu6sb226/legacy%20code%20-%202009.djvu
Testing
By Sergey Lobin
Testing
- 357