Upgrade to Pro — share decks privately, control downloads, hide ads and more …

`Huh?` to `Aha!`: A Refactoring Story

`Huh?` to `Aha!`: A Refactoring Story

Blog post: https://overfullstack.ga/posts/huh-to-aha

## Abstract
**As a Dev, I need to Refactor, to make codebase Refactorable**. Let's achieve this goal through a least spoken, yet most effective Method - **Component Isolation**. Let's discuss how to overcome major **Obstacles** that hinder this goal, and **Metrics** that help measure our success.

## Audience & Take-aways
- This applies to software developers at all levels. I use **Java** to demonstrate the code snippets, but this talk is about Software Design and is agnostic of programming language.
- Refactoring is **NOT** moving lines of code around with IDE shortcuts. The **USP** for this talk is, it takes a unique approach to Refactoring i.e., Component Isolation and provides persuasive arguments against factors that cause __quantum-entanglement__ among components.
- Take-aways for the audiences are:
- How isolated components can build long-lasting structures, keeping rate of entropy growth in check.
- How to use **MOM** process (Methods, Obstacles and Metrics), to build a case for your Refactoring story and pitch it to your team and Manager for funding.
- How to eliminate __Exceptions__ from your code using **ADTs (Algebraic Data Types)**.
- How to replace __Shared Global mutable state on your Shared Code__ with **Immutability** and **Data Transformations**.
- Demo a hello-real-world service with these problems and how applying these simple methods can change the face of this service, thereby reducing the cognitive complexity and organically boosting the testability.
- Metric-driven approach to objectively realize the results and improvements achieved.

Gopal S Akshintala

June 21, 2021
Tweet

More Decks by Gopal S Akshintala

Other Decks in Programming

Transcript

  1. I'm G pa S ks in al ! S S

    e E r @ S r ! @G s a ! o l .g 2
  2. A a ev, I ee t R fa to ,

    t m ke od ba e ef ct ra le @GopalAkshintala overfullstack.ga 5
  3. H w o it h ou S or f r

    un in ? @GopalAkshintala overfullstack.ga 7
  4. M th ds O st cl s M tr cs

    @GopalAkshintala overfullstack.ga 8
  5. Methods Metrics ! Human Readability " Cognitive Complexity ! Component

    Isolation " Testability Obstacles ! Exceptions ! Mutability @GopalAkshintala overfullstack.ga 12
  6. L t's ta t it o r O st cl

    s @GopalAkshintala overfullstack.ga 13
  7. E ce ti ns re he os A us d

    l ng ag f at re Catch or Rethrow anything try { !!" } catch(NullPointerException | IllegalArgumentException | IndexOutOfBoundsException e) { e.printStackTrace(); } try { !!" } catch(Exception e) { throw e; } @GopalAkshintala overfullstack.ga 15
  8. O st cl - 1 - E ce ti ns

    E ce ti ns re or ho es ! @GopalAkshintala overfullstack.ga 16
  9. E ce ti ns re or ho es Tightly coupled

    to not just the caller, but the entire call hierarchy void verifyUserAccess(String userId) throws NoAccessException { if (!hasAccess1(userId)) { throw new NoAccessException(ACCESS_1); } if (!hasAccess2(userId)) { throw new NoAccessException(ACCESS_2); } if (!hasAccess3(userId)) { throw new NoAccessException(ACCESS_3); } } @GopalAkshintala overfullstack.ga 18
  10. I’m so at d ! Optional<NoAccessException> verifyUserAccess(String userId) { if

    (!hasAccess1(userId)) { return Optional.of(new NoAccessException(ACCESS_1)); } if (!hasAccess2(userId)) { return Optional.of(new NoAccessException(ACCESS_2)); } if (!hasAccess3(userId)) { return Optional.of(new NoAccessException(ACCESS_3)); } return Optional.empty(); } @GopalAkshintala overfullstack.ga 19
  11. R pl ce xc pt on w th DT ‣

    Optional - some OR none ‣ Tuple (VAVR) - 1 WITH/AND 2 ‣ Either (VAVR) - left OR right ‣ With the advent of Pattern Matching in Java 16+, these blend more naturally! @GopalAkshintala overfullstack.ga 20
  12. E ce ti ns is se t r tu n

    ul ip e D ta yp s int parse(String s) { if (s.matches("-?[0-9]+")) { return Integer.parseInt(s); } throw new NumberFormatException("Not a valid integer"); } @GopalAkshintala overfullstack.ga 21
  13. R pl ce xc pt on it E th r

    !" Before int parse(String s) { if (s.matches("-?[0-9]+")) { return Integer.parseInt(s); } throw new NumberFormatException("Not a valid integer"); } !" After Either<NumberFormatException, Integer> parse(String s) { if (s.matches("-?[0-9]+")) { return Either.right(Integer.parseInt(s)); } return Either.left(new NumberFormatException("Not a valid integer")); } @GopalAkshintala overfullstack.ga 22
  14. O st cl - 1 - E ce ti ns

    T ro a ay xc pt on @GopalAkshintala overfullstack.ga 23
  15. O st cl - 2 M ta le ta e

    @GopalAkshintala overfullstack.ga 24
  16. S ar d ut bl s at o a S

    ar d od ba e @GopalAkshintala overfullstack.ga 26
  17. O st cl - 2 - M ta il ty

    M ta le bj ct a I pu P ra s ou le om on nt @GopalAkshintala overfullstack.ga 27
  18. static int sum(List<Integer> nums) { int result = 0; for

    (int num : nums) { result += num; } return result; } @GopalAkshintala overfullstack.ga 28
  19. static int sumAbsolute(List<Integer> nums) { for (int i = 0;

    i < nums.size(); i!") { nums.set(i, Math.abs(nums.get(i))); } return sum(nums); !# DRY } @GopalAkshintala overfullstack.ga 29
  20. static void client() { var nums = Arrays.asList(-2, 5, -6);

    System.out.println(sumAbsolute(nums)); } ! GA release! "# @GopalAkshintala overfullstack.ga 30
  21. static void client() { var nums = Arrays.asList(-2, 5, -6);

    System.out.println(sumAbsolute(nums)); System.out.println(sum(nums)); !" 13 } @GopalAkshintala overfullstack.ga 31
  22. static int sumAbsolute(List<Integer> nums) { for (int i = 0;

    i < nums.size(); i!") { nums.set(i, Math.abs(nums.get(i))); !# Latent Bug } return sum(nums); !# DRY } static void client() { List<Integer> nums = Arrays.asList(-2, 5, -6); System.out.println(sumAbsolute(nums)); System.out.println(sum(nums)); !# } @GopalAkshintala overfullstack.ga 32
  23. O st cl - 2 - M ta il ty

    M ta le bj ct a i pu p ra s s nh ly f r so at on @GopalAkshintala overfullstack.ga 33
  24. O st cl - 2 - M ta il ty

    H w bo t ut bl O je ts a et rn yp s? @GopalAkshintala overfullstack.ga 34
  25. M ta le bj ct a R tu n yp

    s Date getEggLayingDate(int eggId) { !" heavy operation return queryEggLayingDateFromDB(eggId); } @GopalAkshintala overfullstack.ga 35
  26. !" Dependent component - 1 boolean isLaidInFirstHalf(int eggId) { var

    layingDate = getEggLayingDate(eggId); if (layingDate.getDate() < 15) { return true; } return false; } !" Dependent component - 2 int calculateEggAge(int eggId, Date today) { return today.getDate() - getEggLayingDate(eggId).getDate(); } @GopalAkshintala overfullstack.ga 36
  27. O e ay! !" Dependent component - 1 boolean isLaidInFirstHalf(int

    eggId) { var layingDate = getEggLayingDate(eggId); if (layingDate.getDate() < 15) { !" It's just logging, let's reuse the same Date obj for month and year layingDate.setDate(15); logger.info("This egg was laid before: " + layingDate); return true; } return false; } @GopalAkshintala overfullstack.ga 37
  28. A ot er ay! static final Map<Integer, Date> eggLayingDateCacheById =

    new HashMap!"(); !# Cache Date getEggLayingDate(int eggId) { return eggLayingDateCacheById .computeIfAbsent(eggId, this!$queryEggLayingDateFromDB); } @GopalAkshintala overfullstack.ga 38
  29. A ot er ay! !" Dependent component - 1 boolean

    isLaidInFirstHalf1(int eggId) { var layingDate = getEggLayingDate(eggId); if (layingDate.getDate() < 15) { !" It's just logging, let's reuse the same Date obj for month and year layingDate.setDate(15); !" Mutation ! logger.info("This egg was laid before: " + layingDate); return true; } return false; } !" Dependent component - 2 long calculateEggAge(int eggId, Date today) { return today.getDate() - getEggLayingDate(eggId).getDate(); !" What did I do? } @GopalAkshintala overfullstack.ga 39
  30. O st cl - 2 - M ta il ty

    T at’s ua tu E ta gl me t ⚛ @GopalAkshintala overfullstack.ga 40
  31. O st cl - 2 - M ta il ty

    B t hy s ut bi it p ed mi an i J va od ? @GopalAkshintala overfullstack.ga 42
  32. P we o D fa lt M ta il ty

    s he ef ul m de n av @GopalAkshintala overfullstack.ga 43
  33. O st cl - 2 - M ta il ty

    M ke mm ta il ty ou D fa lt @GopalAkshintala overfullstack.ga 44
  34. I t ke D sc pl ne o ea t

    e ef ul 1 1 Designed by rawpixel.com / Freepik @GopalAkshintala overfullstack.ga 45
  35. S me ui k in ! ‣ Make a habit

    to use final before var and function params. ‣ Follow Immutable strategy for POJOs from Oracle’s Documentation ‣ (Pre Java 16) Auto-generate Immutable version of your POJO using: ‣ Lombok (More Magic, Less Effort) ‣ Google Auto (Less Magic, More Effort) ‣ Immutables (Less Magic, More Effort) ‣ Use Record types from Java 16 @GopalAkshintala overfullstack.ga 46
  36. O st cl - 2 - M ta il ty

    W at o nt -I mu ab es s y? @GopalAkshintala overfullstack.ga 47
  37. O st cl - 2 - M ta il ty

    - A ti-i mu ab es I n't mm ta il ty nl f r ul i-t re di g? @GopalAkshintala overfullstack.ga 48
  38. I mu ab li y sn't nl f r C

    nc rr nc @GopalAkshintala overfullstack.ga 49
  39. O st cl - 2 - M ta il ty

    - A ti-i mu ab es I mu ab e bj ct d es ’t t y mp ra iv s yl ! @GopalAkshintala overfullstack.ga 50
  40. M ta il ty & I pe at vi y

    re ri nd ! @GopalAkshintala overfullstack.ga 51
  41. M ta il ty ik s o e it I

    pe at vi y void mutableFn() { var mutableList = Arrays.asList("a", "b", "c"); mutateList(mutableList); } List<String> mutateList(List<String> list) { for (var i = 0; i < list.size(); i!") { list.set(i, list.get(i).toUpperCase()); } return list; } @GopalAkshintala overfullstack.ga 52
  42. I pe at ve ut ti ns ep ac d

    y D cl ra iv T an fo ma io s void immutableFn() { final var immutableList = List.of("a", "b", "c"); transformList(immutableList); } List<String> transformList(final List<String> list) { !" `toList()` is new in Java 16 to collect Stream into UnmodifiableList. return list.stream().map(String!#toUpperCase).toList(); } @GopalAkshintala overfullstack.ga 53
  43. I mu ab li y & T an fo ma

    io a e C up e! N C oi e! @GopalAkshintala overfullstack.ga 54
  44. I mu ab li y or es ra sf rm

    ti n void immutableFn() { final var immutableList = List.of("a", "b", "c"); !" ! Throws UnsupportedOperationException mutateList(immutableList); } @GopalAkshintala overfullstack.ga 55
  45. O st cl - 2 - M ta il ty

    - A ti-i mu ab es D es 't mm ta il ty e t er ? @GopalAkshintala overfullstack.ga 56
  46. I mu ab li y nd er or an e

    ctrl-c + ctrl-v from Oracle’s Documentation ‣ The impact of object creation is often overestimated. ‣ It can be offset by decreased overhead due to garbage collection. @GopalAkshintala overfullstack.ga 57
  47. J va’s mb ac ng mm ta il ty, s

    ow y ‣ Most used Data type in any Java application? ‣ String - No coincidence that it’s Immutable. ‣ Java 8 replaced Date with immutable LocalDate. ‣ Java 11 introduced Immutable Collections. ‣ Java 16 introduced Record types, toList on Stream for UnmodifiableList ‣ And many moreG @GopalAkshintala overfullstack.ga 58
  48. H w as he od ? ! void filterDuplicates(Map<ID, Failure>

    failureMap, List<Egg> eggsFromRequest) {!!"} void validate(Map<ID, Failure> failureMap, List<Egg> nonDuplicateEggs) {!!"} List<EggEntity> toEntityObjs(List<Egg> validEggs) {!!"} void bulkInsertIntoDB(List<EggEntity> eggEntityObjs) throws DMLOperationException {!!"} @GopalAkshintala overfullstack.ga 64
  49. P rt ti n et ee S ep filterDuplicates(failureMap, eggsFromRequest);

    !!" !!" !# Partition `eggsFromRequest` using `failureMap` !# into `duplicateEggs`, `nonDuplicateEggs` !!" !!" validate(failureMap, nonDuplicateEggs); @GopalAkshintala overfullstack.ga 65
  50. P rt ti n et ee S ep filterDuplicates(failureMap, eggsFromRequest);

    !" Partition var nonDuplicateEggs = new ArrayList<Egg>(); for (Egg egg : eggsFromRequest) { if (!failureMap.containsKey(egg.getId())) { nonDuplicateEggs.add(egg); } } validate(failureMap, nonDuplicateEggs); @GopalAkshintala overfullstack.ga 66
  51. H w as t? ! filterDuplicates(failureMap, eggsFromRequest); !!" !# Partition

    !!" validate(failureMap, nonDuplicateEggs); !!" !# Partition !!" var eggEntityObjs = toEntityObjs(validEggs); try { bulkInsertIntoDB(eggEntityObjs); } catch (DMLOperationException ex) { handlePartialFailures(failureMap, ex); } !# Prepare response from `failureMap` and db insertion results @GopalAkshintala overfullstack.ga 67
  52. T e ig at re hi t !" Before void

    filterDuplicates(Map<ID, Failure> failureMap, List<Egg> eggsFromRequest) {!!#} !" After static List<Either<Tuple2<ID, Failure>, ImmutableEgg!$ filterDuplicates( final List<ImmutableEgg> eggsFromRequest) {!!#} @GopalAkshintala overfullstack.ga 69
  53. U in E th r or ar ia F il

    re List<Either<Tuple2<ID, Failure>, ImmutableEgg>> @GopalAkshintala overfullstack.ga 70
  54. P rt al ai ur s ow s re m

    ↡ List<Either<Tuple2<ID, Failure>, ImmutableEgg>> @GopalAkshintala overfullstack.ga 71
  55. V li at ng n it er !" Before void

    validate(Map<ID, Failure> failureMap, List<Egg> nonDuplicateEggs) {!!#} !" After static Either<Tuple2<ID, Failure>, ImmutableEgg> validate( final Either<Tuple2<ID, Failure>, ImmutableEgg> egg) {!!#} @GopalAkshintala overfullstack.ga 72
  56. V li at ng n it er static Either<Tuple2<ID, Failure>,

    ImmutableEgg> validate( Either<Tuple2<ID, Failure>, ImmutableEgg> eggToValidate) { return eggToValidate !" No validations execute if egg is Either.left .flatMap(validateAge) .flatMap(validateField1) !!# !!# ; } @GopalAkshintala overfullstack.ga 73
  57. 74

  58. R fa to ed - N S ri gs tt

    ch d! !" void filterDuplicates(Map<ID, Failure> failureMap, List<Egg> eggsFromRequest) {!!#} static List<Either<Tuple2<ID, Failure>, ImmutableEgg!$ filterDuplicates(final List<ImmutableEgg> eggsFromRequest) {!!#} !" void validate(Map<ID, Failure> failureMap, List<Egg> nonDuplicateEggs) {!!#} static Either<Tuple2<ID, Failure>, ImmutableEgg> validate(final Either<Tuple2<ID, Failure>, ImmutableEgg> egg) {!!#} !" List<EggEntity> toEntityObjs(List<Egg> validEggs) {!!#} static Either<Tuple2<ID, Failure>, EggEntity> toEntityObj(final Either<Tuple2<ID, Failure>, ImmutableEgg> egg) {!!#} !" Partition !" void bulkInsertIntoDB(List<EggEntity> eggEntityObjs) throws DMLOperationException {!!#} static Either<DMLOperationException, List<EggEntity!$ bulkInsertIntoDB(final List<EggEntity> eggEntityObjs) {!!#} @GopalAkshintala overfullstack.ga 75
  59. L ke M th er va io static List<Either<Tuple2<ID, Failure>,

    ImmutableEgg!" filterDuplicates(final List<ImmutableEgg> eggsFromRequest) {!!#} static Either<Tuple2<ID, Failure>, ImmutableEgg> validate(final Either<Tuple2<ID, Failure>, ImmutableEgg> egg) {!!#} static Either<Tuple2<ID, Failure>, EggEntity> toEntityObj(final Either<Tuple2<ID, Failure>, ImmutableEgg> egg) {!!#} !$ Partition static Either<DMLOperationException, List<EggEntity!" bulkInsertIntoDB(final List<EggEntity> eggEntityObjs) {!!#} @GopalAkshintala overfullstack.ga 76
  60. F nc io C mp si io final var partition

    = filterDuplicates(immutableEggsFromRequest).stream() .map(EggService!"validate) .map(EggService!"toEntityObj) .collect(Collectors.partitioningBy(Either!"isRight)); final var objsToInsert = partition.get(true).stream().map(Either!"get).toList(); final var results = bulkInsertIntoDB(objsToInsert); !# Prepare response from `partition.get(false)` and `results` @GopalAkshintala overfullstack.ga 78
  61. H w re e oi g n ur oa ?

    L t's sk ur et ic @GopalAkshintala overfullstack.ga 79
  62. M tr c - 1 C gn ti e om

    le it @GopalAkshintala overfullstack.ga 80
  63. C gn ti el C mp ex filterDuplicates(failureMap, eggsFromRequest); !!"

    !# Partition !!" validate(failureMap, nonDuplicateEggs); !!" !# Partition !!" var eggEntityObjs = toEntityObjs(validEggs); try { bulkInsertIntoDB(eggEntityObjs); } catch (DMLOperationException ex) { handlePartialFailures(failureMap, ex); } !# Prepare response from `failureMap` and db insertion results @GopalAkshintala overfullstack.ga 81
  64. D d e ⊗ C gn ti e om le

    it ? final var partition = filterDuplicates(immutableEggsFromRequest).stream() .map(EggService!"validate) .map(EggService!"toEntityObj) .collect(Collectors.partitioningBy(Either!"isRight)); final var objsToInsert = partition.get(true).stream().map(Either!"get).toList(); final var results = bulkInsertIntoDB(objsToInsert); !# Prepare response from `partition.get(false)` and `results` @GopalAkshintala overfullstack.ga 82
  65. C mp ex ty ‣ Different types ‣ Accidental Complexity

    ‣ Essential complexity ‣ Cognitive/Cyclomatic complexity ‣ Different layers ‣ Unfamiliarity vs Unreadability. ‣ Strict vs Non-extensible. @GopalAkshintala overfullstack.ga 84
  66. C mp ex ty er ! final var partition =

    filterDuplicates(immutableEggsFromRequest).stream() .map(EggService!"validate) .map(EggService!"toEntityObj) .collect(Collectors.partitioningBy(Either!"isRight)); final var objsToInsert = partition.get(true).stream().map(Either!"get).toList(); final var results = bulkInsertIntoDB(objsToInsert); !# Prepare response from `partition.get(false)` and `results` ! Essential Complexity ! Unfamiliarity ! Strictness @GopalAkshintala overfullstack.ga 85
  67. ! S na Qu e™ T O je ti el

    m as re og it ve om le it ! My previous talks, on the usage of SonarQube™: ! Java Version ! Kotlin Version @GopalAkshintala overfullstack.ga 86
  68. !" * EggEntity is a legacy class and cannot be

    instantiated with `new` void fillEntityObj(Egg egg, EggEntity eggEntity) { if (egg.field1() !# null) { eggEntity.put(Fields.field1, egg.field1()); } if (egg.field2() !# null) { eggEntity.put(Fields.field2, egg.field2()); } if (egg.field3() !# null) { eggEntity.put(Fields.field3, egg.field3()); } } @GopalAkshintala overfullstack.ga 89
  69. B it le es @Test void fillEntityObjTest() { !" *

    `EggEntity` Object can't be instantiated in a JUnit context. final var mockEggEntity = mock(EggEntity.class); var egg = new Egg("field1Value", null, "field3Value"); fillEntityObj(egg, mockEggEntity); verify(mockEggEntity).put(Fields.field1,"field1Value"); verify(mockEggEntity, never()).put(Fields.field2, null); verify(mockEggEntity).put(Fields.field3,"field3Value"); } @GopalAkshintala overfullstack.ga 90
  70. B os y ur es ab li y U it

    es !== T st nt rn ls @GopalAkshintala overfullstack.ga 91
  71. B os y ur es ab li y U it

    es === E2E es f r he ni @GopalAkshintala overfullstack.ga 92
  72. B os y ur es ab li y S pa

    at S at c ro S gn l @GopalAkshintala overfullstack.ga 93
  73. S pa at S at c ro S gn l

    ‣ Static: 1-1 field mapping between Egg to EggEntity ‣ Signal: For each field mapping, fill non-null Egg fields into EggEntity using put. @GopalAkshintala overfullstack.ga 94
  74. S pa at S at c ro S gn l

    !" Static static final Map<String, Function<ImmutableEgg, String!# fieldMapping = Map.of( Fields.field1, ImmutableEgg!$field1, Fields.field2, ImmutableEgg!$field2, Fields.field3, ImmutableEgg!$field3); !" Call with Static data and Function reference fillEntityObj(egg, fieldMapping, EggEntity!$put); !" Signal static void fillEntityObj( ImmutableEgg egg, Map<String, Function<ImmutableEgg, String!# fieldMapping BiConsumer<String, String> filler) { fieldMapping.forEach((fieldId, valueGetter) !% { final var fieldValue = valueGetter.apply(egg); if (fieldValue !& null) { filler.accept(fieldId, fieldValue); } }); } @GopalAkshintala overfullstack.ga 95
  75. T st nl t at at er @Test void fillEntityObjTest()

    { final var egg = ImmutableEgg.of("field1Value", null, "field3Value"); var actualPairs = new HashMap<String, String>(); fillEntityObj(egg, actualPairs!"put); var expectedPairs = Map.of( Fields.field1, "field1Value", Fields.field3, "field3Value"); assertEquals(expectedPairs, actualPairs); } @GopalAkshintala overfullstack.ga 96
  76. T y ot o nj ct nt re bj ct

    @Autowired public EggService( !!", @Qualifier(EGG_REPO) EggRepo eggRepo, !!" ) @GopalAkshintala overfullstack.ga 98
  77. I je t nl t e un ti n @Autowired

    public EggService( !!", !# @Qualifier(EGG_REPO) !# EggRepo eggRepo, @Qualifier(EGG_INSERTER) Function<EggEntity, ID> dbInserter, !!" ) @GopalAkshintala overfullstack.ga 99
  78. P rt & A ap er /C ea A ch

    te tu e w th ut xt a nt rf ce @Autowired public EggService( !!", !# @Qualifier(EGG_REPO) !# EggRepo eggRepo, @Qualifier(EGG_INSERTER) Function<EggEntity, ID> dbInserter, !!" ) !# Original Config @Bean(EGG_INSERTER) public Function<EggEntity, ID> insert( @Qualifier(EGG_REPO) EggRepo eggRepo) { return eggRepo!$insert; } !# Test Config @Bean(EGG_INSERTER) public Function<EggEntity, ID> insertNoOp() { return eggEntity !% new ID(1); !#stub } @GopalAkshintala overfullstack.ga 100
  79. M tr c - 2 - T st bi it

    T st bi it F rs T st ov ra e ol ow @GopalAkshintala overfullstack.ga 101
  80. M tr c - 2 - T st bi it

    T st ! R fa to (L st ut ot he as ) @GopalAkshintala overfullstack.ga 102
  81. R fa to I cr me ta ly A p

    es ri ed y DD @GopalAkshintala overfullstack.ga 106
  82. ! b t.l /h2a-s id s ⌨ b t.l /h2a-c

    de ! B og os @GopalAkshintala overfullstack.ga 108