Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.

Get better at Refactoring (Voxxed Days)

You have found the more you change the code, the more difficult it becomes. Refactoring is a developer practice of improving the internal structure of existing code without changing the external behaviour. It makes code changes for present or future easier. You may have heard about Refactoring from colleagues, books, articles and have probably tried or hear it the first time. This talk presents what it is, why it is important and what you can do to get better at it based on the experience of mentoring on technical practices to others. This talk is suitable for people who are experienced in programming, have met the challenges of making changes and want to deepen their skill. Demo included!

  • Sé el primero en comentar

Get better at Refactoring (Voxxed Days)

  1. 1. GET BETTER AT REFACTORING stanly@odd-e.com
 @stanlylau
  2. 2. INTRODUCTION ABOUT ME ▸ Lives in Singapore ▸ Software development coach / mentor ▸ Goal: Reduce the cost of change ▸ Agile Singapore community
  3. 3. 1. PROBLEM 2. REFACTORING 3. DEMO 4. TIPS
  4. 4. @RequestMapping(value = "/send", method = RequestMethod.POST) public String post(@ModelAttribute MailSendForm form, Model model) { model.addAttribute("form", form); return "send"; } loc: 4
  5. 5. @RequestMapping(value = "/send", method = RequestMethod.POST) public String post(@ModelAttribute MailSendForm form, Model model) { // TODO Validate Request String address = form.getAddress(); boolean isMailAddress = Validator.isMailAddress(address); if (!isMailAddress) { model.addAttribute("form", form); model.addAttribute("error", "error"); return "send"; } String subject = form.getSubject(); boolean isSubject = Validator.isSubject(subject); String body = form.getBody(); boolean isBody = Validator.isBody(body); // TODO Mail Send // TODO Show the page model.addAttribute("form", form); return "send"; } loc: 14
  6. 6. @RequestMapping(value = "/sendEmail", method = RequestMethod.POST) public String sendEmail(@ModelAttribute MailSendForm form, Model model) { if (!Validator.isMailAddress(form.getAddress())) { model.addAttribute("error", "error"); return "send"; } if (!Validator.isSubject(form.getSubject())) { model.addAttribute("error", "error"); return "send"; } if (!Validator.isBody(form.getBody())) { model.addAttribute("error", "error"); return "send"; } MailInfo mail = new MailInfo("gadget.mailsender@gmail.com", form.getAddress(), form.getSubject(), form.getBody()); try { mailService.send(mail); } catch (Exception e) { e.printStackTrace(); } return "send"; } loc: 17
  7. 7. @RequestMapping(value = "/send", method = RequestMethod.POST) public String sendEmail(@Valid @ModelAttribute MailSendForm form, BindingResult result, Model model) { if (result.hasErrors()) { model.addAttribute("errorMessage", "error"); return "send"; } String[] addresses = form.getAddress().split("s*;s*"); boolean isValid = Stream.of(addresses).anyMatch(address -> !Validator.isMailAddress(address)); if (isValid) { model.addAttribute("errorMessage", "error"); return "send"; } for (String address : addresses) { MailInfo mail = new MailInfo("gadget.mailsender@gmail.com", address, form.getSubject(), form.getBody()); try { mailService.send(mail); } catch (Exception e) { model.addAttribute("errorMessage", "error"); return "send"; } } return "send"; } loc: 18
  8. 8. @RequestMapping(value = "/send", method = RequestMethod.POST) public String sendEmail(@ModelAttribute MailSendForm form, Model model) { if (!Validator.isSubject(form.getSubject())) { model.addAttribute("errorMessage", "error"); return "send"; } if (!Validator.isBody(form.getBody())) { model.addAttribute("errorMessage", "error"); return "send"; } String addressFromForm = form.getAddress(); if (StringUtils.isEmpty(addressFromForm)) { model.addAttribute("errorMessage", "error"); return "send"; } String[] addresses = addressFromForm.split(";"); for(String address : addresses) { if (!Validator.isMailAddress(address)) { model.addAttribute("errorMessage", "error"); return "send"; } MailInfo mail = new MailInfo("gadget.mailsender@gmail.com", address, form.getSubject(), form.getBody()); try { mailService.send(mail); } catch (Exception e) { model.addAttribute("errorMessage", "error"); } } return "send"; } loc: 23
  9. 9. @RequestMapping(value = "/send", method = RequestMethod.POST) public String sendEmail(@Valid @ModelAttribute MailSendForm form, BindingResult result, Model model) { if (result.hasErrors()) { model.addAttribute("errorMessage", "error"); return "send"; } String[] addresses = form.getAddress().split(“s*;s*"); boolean isValid = Stream.of(addresses).anyMatch(address -> !Validator.isMailAddress(address)); if (isValid) { model.addAttribute("errorMessage", "error"); return "send"; } try { List<MailInfo> mailInfoList = new ArrayList<>(); String subject = form.getSubject(); for (String address : addresses) { AddressItem addressItem = addressBookService.findByAddress(address); if (addressItem != null) { if (StringUtils.isEmpty(addressItem.getName()) && StringUtils.contains(subject, "$name")) { throw new Exception("name attribute is empty!!"); } subject = StringUtils.replace(subject, "$name", addressItem.getName()); } MailInfo mail = new MailInfo("gadget.mailsender@gmail.com", address, subject, form.getBody()); mailInfoList.add(mail); } mailService.sendMultiple(mailInfoList); } catch (Exception e) { model.addAttribute("errorMessage", "error"); return "send"; } return "send"; loc: 26
  10. 10. @RequestMapping(value = "/send", method = RequestMethod.POST) public String sendEmail(@Valid @ModelAttribute MailSendForm form, BindingResult result, Model model) { if (result.hasErrors()) { model.addAttribute("errorMessage", "error"); return "send"; } String[] addresses = form.getAddress().split("s*;s*"); boolean isValid = Stream.of(addresses).anyMatch(address -> !Validator.isMailAddress(address)); if (isValid) { model.addAttribute("errorMessage", "error"); return "send"; } try { List<MailInfo> mailInfoList = new ArrayList<>(); String subject = form.getSubject(); for (String address : addresses) { AddressItem addressItem = addressBookService.findByAddress(address); String replacedSubject = subject; if (addressItem != null) { if (StringUtils.isEmpty(addressItem.getName()) && StringUtils.contains(subject, "$name")) { throw new Exception("name attribute is empty!!"); } replacedSubject = StringUtils.replace(subject, "$name", addressItem.getName()); }else { if (StringUtils.contains(subject, "$name")){ throw new Exception("email address is not registered"); } } MailInfo mail = new MailInfo("gadget.mailsender@gmail.com", address, replacedSubject, form.getBody()); mailInfoList.add(mail); } mailService.sendMultiple(mailInfoList); } catch (Exception e) { model.addAttribute("errorMessage", "error"); return "send"; } return "send"; loc: 30
  11. 11. @RequestMapping(value = "/send", method = RequestMethod.POST) public String sendEmail(@Valid @ModelAttribute MailSendForm form, BindingResult result, Model model) { if (result.hasErrors()) { FieldError fieldError = result.getFieldErrors().get(0); model.addAttribute("errorMessage", fieldError.getField() + " " + fieldError.getDefaultMessage()); return "send"; } String[] addresses = form.getAddress().split("s*;s*"); boolean isValid = Stream.of(addresses) .anyMatch(address -> !Validator.isMailAddress(address)); if (isValid) { model.addAttribute("errorMessage", "Email address is invalid"); return "send"; } try { List<MailInfo> mailInfoList = new ArrayList<>(); String subject = form.getSubject(); String body = form.getBody(); for (String address : addresses) { Contact addressItem = addressBookService.findByAddress(address); String replacedSubject = subject; String replacedBody = body; if (addressItem != null) { if ("".equals(addressItem.getName()) && (subject.contains("$name") || body.contains("$name"))) { throw new Exception("Can't render template with an empty name in contact"); } replacedSubject = subject.replace("$name", addressItem.getName()); replacedBody = body.replace("$name", addressItem.getName()); }else { if (subject.contains("$name") || body.contains("$name")){ throw new Exception("No matching contact found to render template"); } } MailInfo mail = new MailInfo("gadget.mailsender@gmail.com", address, replacedSubject, replacedBody); mailInfoList.add(mail); } mailService.sendMultiple(mailInfoList); } catch (Exception e) { model.addAttribute("errorMessage", e.getMessage()); return "send"; } return "send"; } loc: 33
  12. 12. http://www.marketingfirepower.info
  13. 13. A NATURE OF SOFTWARE DEVELOPMENT AMOUNT OF "BAD” CODE # BUGS TIME SPENT ON FIXING BUGS PANIC QUICK HACKS MOTIVATION OF DEVELOPERS CASUAL LOOP DIAGRAM
  14. 14. A NATURE OF SOFTWARE DEVELOPMENT REQUIREMENTS TIME (you) (competitor) COMPETING BY CHANGE
  15. 15. The Management of Computer Programming Projects Charles Lecht - 1967 Equally responsible for the initiation of project with predefined failure is management that insists upon having fixed commitments from programming personnel prior to the latter’s understanding what the commitment are for. Too frequently, management does not realize that in asking the staff for “the impossible”, the staff will feel the obligation to respond out of respect, fear or misguided loyalty. Saying “no” to the boss frequently requires courage, political and psychological wisdom, and business maturity that comes with much experience.
  16. 16. A NATURE OF SOFTWARE DEVELOPMENT AMOUNT OF "BAD” CODE # BUGS TIME SPENT ON FIXING BUGS PANIC QUICK HACKS MOTIVATION OF DEVELOPERS AMOUNT OF CODE SMELLS REFACTORING OPPORTUNITY FOR INDICATES
  17. 17. REFACTORING 101 SOFTWARE MAINTAINABILITY
  18. 18. REFACTORING “…IS A DISCIPLINED TECHNIQUE FOR RESTRUCTURING AN EXISTING BODY OF CODE, ALTERING ITS INTERNAL STRUCTURE WITHOUT CHANGING ITS EXTERNAL BEHAVIOR.” MARTIN FOWLER INTRODUCE PARAMETER OBJECT EXTRACT CLASS INTRODUCE LOCAL VARIABLE RENAME MOVE METHODEXTRACT METHOD INLINE VARIABLE
  19. 19. Q: WHEN DO WE REFACTOR? A: WHEN THE CODE SMELLS!
  20. 20. CODE SMELLS “A CODE SMELL IS A SURFACE INDICATION THAT USUALLY CORRESPONDS TO A DEEPER PROBLEM IN THE SYSTEM” MARTIN FOWLER DUPLICATED CODE MAGIC NUMBER FEATURE ENVY PRIMITIVE OBSESSION LONG METHOD COMMENTS
  21. 21. ABOUT DEMO PERSONALISED EMAILER WEB APP
  22. 22. JAVA SPRING MVC
  23. 23. RECEIVED (NORMAL) TO: NON-CONTACT@GMAIL.COM SUBJECT: SEASONS GREETING TO: NON-CONTACT2@GMAIL.COM SUBJECT: SEASONS GREETING
  24. 24. RECEIVED (TEMPLATE) TO: STANLY@ODD-E.COM SUBJECT: SEASONS GREETING STANLY TO: AKI@ODD-E.COM SUBJECT: SEASONS GREETING AKI
  25. 25. Subject with attribute Body with attribute Send to multiple recipients … ✓ ✓
  26. 26. @RequestMapping(value = "/send", method = RequestMethod.POST) public String sendEmail(@Valid @ModelAttribute MailSendForm form, BindingResult result, Model model) { if (result.hasErrors()) { FieldError fieldError = result.getFieldErrors().get(0); model.addAttribute("errorMessage", fieldError.getField() + " " + fieldError.getDefaultMessage()); return "send"; } String[] addresses = form.getAddress().split("s*;s*"); boolean isValid = Stream.of(addresses) .anyMatch(address -> !Validator.isMailAddress(address)); if (isValid) { model.addAttribute("errorMessage", "Email address is invalid"); return "send"; } try { List<MailInfo> mailInfoList = new ArrayList<>(); String subject = form.getSubject(); String body = form.getBody(); for (String address : addresses) { Contact addressItem = addressBookService.findByAddress(address); String replacedSubject = subject; String replacedBody = body; if (addressItem != null) { if ("".equals(addressItem.getName()) && (subject.contains("$name") || body.contains("$name"))) { throw new Exception("Can't render template with an empty name in contact"); } replacedSubject = subject.replace("$name", addressItem.getName()); replacedBody = body.replace("$name", addressItem.getName()); }else { if (subject.contains("$name") || body.contains("$name")){ throw new Exception("No matching contact found to render template"); } } MailInfo mail = new MailInfo("gadget.mailsender@gmail.com", address, replacedSubject, replacedBody); mailInfoList.add(mail); } mailService.sendMultiple(mailInfoList); } catch (Exception e) { model.addAttribute("errorMessage", e.getMessage()); return "send"; } return "send"; }
  27. 27. WHAT DID YOU SMELL?
  28. 28. WHAT DID YOU SMELL? L O N G M E T H O D
  29. 29. MailController
 ——————
 sendEmail() MailInfo AddressBookService
 (interface)
 ———————
 findAddress() MailService
 (interface)
 ——————
 sendMultiple() MailSendForm
  30. 30. LET’S REFACTOR sendEmail() CODE COVERAGE ✓
  31. 31. MailController
 ——————
 sendEmail() Mail ContactDao
 (interface)
 ———————
 findAddress() MailService
 (interface)
 ——————
 sendMultiple() EmailTemplate
 ————-————
 populate()
 hasPopulateError()
 getPopulateErrorMessage()
  32. 32. CODE SMELLS LONG METHOD TEMPORARY VARIABLE FEATURE ENVY DUPLICATE CODE MAGIC NUMBER UNCOMMUNICATIVE NAME PRIMITIVE OBSESSION
  33. 33. sendEmail() BREAKS SINGLE RESPONSIBILITY MAIL CREATION ERROR HANDLING TEMPLATE RENDERING VALIDITY OF RECIPIENT ADDRESS
  34. 34. REFACTORING EXTRACT METHOD INLINE VAR. / METHOD INTRODUCE PARAMETER RENAME MOVE METHOD
  35. 35. TIPS
  36. 36. LEARN TO SMELL THE CODE CODE SMELLS
  37. 37. DESIGN PRINCIPLES SINGLE RESPONSIBILITY HI COHESION, LOW COUPLING
  38. 38. while(change_for_new_functionality()){ DO_NOT_REFACTOR() }
  39. 39. PROGRAMMING IS THE ART OF DOING ONE THING AT A TIME Michael Feathers TIPS
  40. 40. REFACTORING WHY? WHAT? HOW? GET READY FOR CHANGE DESIGN CODE SMELLS
  41. 41. HOW TO START ESSENTIAL SKILLS ▸ Explain the code with design principles ▸ Identify code smells ▸ Refactor either manually or through IDE ▸ Familiar working with unit/integration tests ▸ Familiar with the functionality you’re changing
  42. 42. PRACTICE REFACTORING KATA https://github.com/stanlylau/tictactoe_csharp https://github.com/stanlylau/refactoring-kata https://github.com/emilybache/Tennis-Refactoring-Kata https://github.com/emilybache/GildedRose-Refactoring-Kata https://github.com/stanlylau/trivia-csharp
  43. 43. TEACH TO LEARN 1. CREATE CODE SMELLS EXERCISES 2. PRACTICE REMOVING THE SMELLS 3. INVITE PEOPLE TO REMOVE THE SMELLS 4. CODE REVIEW 5. DEMO
  44. 44. Unconscious
 Competence Conscious
 Competence Conscious
 Incompetence Unconscious
 Incompetence FOUR STAGES OF COMPETENCE
  45. 45. TO LEVEL UP REVIEW THE CODE WITH A MENTOR, BE REFLECTIVE
  46. 46. EXPERIENCED PROGRAMMERS DO NOT WRITE INFINITELY COMPLEX CODE; THEY WRITE CODE THAT’S BLINDINGLY SIMPLE 99 Bottles of OOP TIPS
  47. 47. REFACTORING IS AN EXERCISE IN KINDNESS Kent Beck
  48. 48. THANK YOU STANLY@ODD-E.COM

×