Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

Legacy is Good

Presented at jDD Krakow Oct 2017
Developers love to complain about their legacy systems, but if you have a legacy system, it means you have a working, successful system which deliver real value to business, and this is definitely a good thing.

  • Identifiez-vous pour voir les commentaires

Legacy is Good

  1. 1. GreedGreed,, for lack of a better word,for lack of a better word, is Goodis Good Uberto Barbini @ramtop LegacyLegacy
  2. 2. My Twitter and Medium handle is @ramtop if you know what it means, either you are a Terry Pratchett fan or... ...you started programming on this thing:
  3. 3. The most scary words in a project description: Brownfield DevelopmentBrownfield Development ➔ Everybody hates itEverybody hates it ➔ Just fix the minimumJust fix the minimum ➔ Necessary EvilNecessary Evil
  4. 4. An Inspiration from Japan
  5. 5. Cost for Feature: change your line!
  6. 6. Legacy System Problems
  7. 7. ✔ Obsolete technology ✔ Don’t scale to new requirements ✔ Hard to change ✔ Expensive to run and maintain
  8. 8. Technical Debt Default
  9. 9. Working Overload
  10. 10. Temporary unsafe solutions “I fixed it!”
  11. 11. Continuous State of Emergency
  12. 12. We cannot retire it because we don’t know who is using it..
  13. 13. The Usual Solution and Its Problems
  14. 14. Forget the old system! let’s rewrite it from scratch shiny and new! (music) Shiny! Shiny! Like a treasure from a sunken pirate wreck
  15. 15. Is new always better?
  16. 16. .. and since we are rewriting it we can add a new feature or two...
  17. 17. c Hidden costs of rewrite ● Legacy system complexity is always underestimated because of success bias ● All previous knowledge already in the code, would be throw away and then slowly (and costly) rediscovered ● New features can delay the release ● Old system get neglected but still stays critical ● Data migration is always more painful than expected
  18. 18. Distressing Position What’s happen if you got stuck between a neglected legacy and a not properly working new system?
  19. 19. Forgetting a small detail can be dangerous..
  20. 20. New Rewrite Left Unfinished
  21. 21. We just need another adapter...
  22. 22. A Different Approach
  23. 23. "Legacy code" often differs from its suggested alternative by actually working and scaling Bjarne Stroustrup
  24. 24. First decide a safe route The big change will be delivered in small incremental milestones Each milestone must: ● Solve at least one problem ● Take a few weeks at most ● Be a stable solution in itself ● Be an improvement on previous version
  25. 25. Safe doesn’t mean straight! It could take more time than a straight rewrite, at least apparently.
  26. 26. A little exercise: which steps we can use for these routes? ● From a C/S application on DBMS to HTML5 with Rest backend running on NoSql ● From monolithic Java backend to Microservices in different languages ● From several different applications to a centralized dashboard ● From an MVP website to a SPA rich client
  27. 27. Alchemical Alchemical  RejuvenationRejuvenation First you seal everything with First you seal everything with  external testsexternal tests Then you split it in modulesThen you split it in modules Now you clean the module you Now you clean the module you  need to work withneed to work with Unit tests help to keep it cleanUnit tests help to keep it clean Repeat many times if necessaryRepeat many times if necessary I called this process I called this process  Alchemical RejuvenationAlchemical Rejuvenation
  28. 28. What really was Alchemy?
  29. 29. Step 0: simplify the on-boarding Ask your DevOps friends for advice
  30. 30. Wrap it with end to end tests the important thing about e2e test is where to put the two ends
  31. 31. Split in modules Remember to always cut vertically, not horizontally
  32. 32. Refactor Spaghetti Code with gusto!
  33. 33. Then everything is tidy and tested
  34. 34. Time to Restart with Another Module! ...at least if we have to
  35. 35. Visual Recipe
  36. 36. The Technical Part (code examples)
  37. 37. Legacy Code Antipatterns ● Stovepipes almost identical code in different systems ● Lavaflow trail of out-of-fashion code from forgotten specs ● Spaghetti everything depends on everything else ● God class a class that does way too much ● Intrusive frameworks (Spring, EJB, etc.) sometimes they are more a problem than a solution
  38. 38. Michael Feathers Working Effectively with Legacy Code
  39. 39. Refactoring to Patterns Joshua Kerievsky
  40. 40. Hermetic Seal GOAL Split the code in two  modules. HOW Wrap the code you want to  keep apart behind an interface  working as Façade It’s ok if it makes little sense as  design because we put it there  only to avoid involuntary  changes, we can remove it later. New code can be either inside the  façade (mocked) or the one calling  the façade (mocks), but not both.
  41. 41. Original codeprivate ConnHelper connHelper; private ConfigHelper configHelper; public Response execute(client, market, trades, connections){ Connection con = connHelper.connect(client, connections); Context context = con.fetchContext(market); Portfolio pf = new Portfolio(client, trades); Options opts = configHelper.getOptions(client, context); CalcResult res = runCalculations(pf, con, opts, context); return new Response(context, res); } The problem is that we don’t want to touch Connection and Config in a different module
  42. 42. Step One Let’s create a new module and its façade without changing anything else interface DataContext(){ Connection getConnection(); Options getOptions(); Context getContext(); } private DataContextBuilder newModule; public ExplainResults execute(client, market, trades, conns){ Portfolio pf = new Portfolio(client, trades); DataContext dc = newModule.createDataContext(pf, market); CalcResult r = runCalculations(pf, dc.getConnection(), dc.getOptions(), dc.getContext()); return new Response(dc.getContext(), r); }
  43. 43. Step Two let’s hide Options looking inside runCalculations where is used: ... double x = options.calculate(portfolio); ... Let's expose the method and hide the field interface DataContext(){ Connection getConnection(); double calculate(Portfolio portfolio); Context getContext(); }
  44. 44. Step Three let’s hide Connection Where it’s used? looking inside runCalculations: ... newPortfolio = context.applyTranform(conn, portfolio); ... Let's do the same. Since Context and Connection are already in DataContext we don’t need to expose them interface DataContext(){ double calculate(Portfolio portfolio); Portfolio applyTranform(Portfolio portfolio); Context getContext(); }
  45. 45. After private CalcResult runCalculations(Portfolio portfolio, DataContext dc) { //some very complex calculations Portfolio newPortfolio = dc.applyTranform(portfolio); //others very complex calculations double x = dc.calculate(newPortfolio); return CalcResult.success(x); } private CalcResult runCalculations(Portfolio portfolio, Connection conn, Options opts, Context context) { //some very complex calculations Portfolio newPortfolio = context.applyTranform(conn, portfolio); //others very complex calculations double x = opts.calculate(newPortfolio); return CalcResult.success(x); } Before
  46. 46. Finished! private DataContextBuilder newModule; //injected with CP public Response execute(client, market, trades){ Portfolio pf = new Portfolio(client, trades); DataContext dc = newModule.createDataContext(market); CalcResult r = runCalculations(pf, dc); return new CalcResults(dc, r); } Now we can test execute in insulation. We can do the same everywhere Connections and Options are used. We can remove connectionPool parameter as well
  47. 47. Solve Et Coagula GOAL Simplify the  interactions in a group of  closely related classes. HOW Transform all the  methods in all the classes to  static ones. Then refactor  them trying to reduce the  number of parameters. Then  move them to the closest class  and make them non static.
  48. 48. The original code public class Main { public static void main(String[] args){ Address a = new Address("777 No Street", "London, Uk", "WWW-777"); Client c = new Client("Mr", "James Bond", a); Printer p = new Printer(); Label label = new Label(p, a); p.printLabel(c, label); } } output +---------------+ | Mr James Bond | | 777 No Street | | London, Uk | | WWW-777 | +---------------+
  49. 49. public class Client { public Address address; public String title; public String fullName; public Client(String title, String fullname, Address address) { this.title = title; this.fullName = fullname; this.address = address; this.address.client = this; } } public class Printer { public void printLabel(Client u, Label l){ if (u != null && u.address != null) l.sendToPrinter(); } public void printLine(String line){ System.out.println(line); //simulate sending } } public class Address { public Client client; private String street; private String city; private String areaCode; public Address(String street, String city, String areaCode) { this.street = street; this.city = city; this.areaCode = areaCode; } public String[] getLines() { String[] lines = new String[4]; lines[0] = client.title + " " + client.fullName; lines[1] = street; lines[2] = city; lines[3] = areaCode; return lines; } } public class Label { private Address address; private Printer printer; public Label(Printer printer, Address address) { this.printer = printer; this.address = address; } public void sendToPrinter() { int mx = 0; for (String line : address.getLines()) { mx = mx > line.length() ? mx: line.length(); } String side = "+" + createString(mx - 2, "-") + "+"; printer.printLine(side); for (String line : address.getLines()) { String spaces = createString(mx - line.length() - 4, " "); printer.printLine("| " + line + spaces + " |"); } printer.printLine(side); } private String createString(int length, String ofChar) { return new String(new char[length + 4]).replace("0", ofChar); } }
  50. 50. Step One - Solve all methods become static in a new class private fields stay in the class public class Functions { public static String[] getLines(Address a) {…} public static void sendToPrinter(Address a, Printer p) {…} public static void printLabel(Client u, Printer p){…} public static void printLine(Printer p, String line){…} }
  51. 51. Step Two - Coagula public class Client { … public String[] getLines() {…} } public class Label { … public static void print(Printer p, Client c){…} } public class Printer { … public void printLine(String line){…} } Methods go back to the closest class, or stay static if no such class exists.
  52. 52. A possible final result public class Main { public static void main(String[] args){ Address a = new Address("777 No Street", "London, Uk", "WWW-777"); Client c = new Client("Mr","James Bond",a); Printer p = new Printer("usb"); Label.print(p, c); } } The complete code for these examples and others can be found at: https://github.com/uberto/alchemical
  53. 53. Legacy can be Good!
  54. 54. The Real Software Architect An Ounce of Prevention Is Worth a Pound of Cure
  55. 55. Legacy degradation: Slow Boiling Frog
  56. 56. Further degration: Law of Broken Windows
  57. 57. To invert entropy
  58. 58. Plan the whole lifecycle each module should specify also who is using it and how to remove it if you have to.
  59. 59. Vitruvius Three principles of good Architecture
  60. 60. If you are interested contact me! I’d love to hear experiences I am collecting material for a book Please follow me @ramtop medium.com/@ramtop www.gama-soft.co.uk