├── .gitignore ├── README.md ├── WorkingEffectivelyWithLegacyCodeExamples.iml ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── out └── .gitignore └── src ├── main └── java │ └── com │ └── thoughtworks │ └── legacycode │ ├── adapt_parameter │ ├── Main.java │ └── ReportGenerator.java │ ├── break_out_method_object │ ├── Main.java │ ├── Monster.java │ ├── Physics.java │ └── Rendering.java │ ├── encapsulate_global_reference │ ├── Book.java │ ├── Library.java │ └── Main.java │ ├── exercise │ ├── Defragger.java │ ├── DiskUtils.java │ ├── FileManager.java │ ├── FilePrinter.java │ ├── Main.java │ ├── Partition.java │ └── PartitionDoesNotExistException.java │ ├── expose_static_method │ ├── Book.java │ ├── Library.java │ └── Main.java │ ├── extract_and_override_call │ ├── Book.java │ ├── Library.java │ └── Main.java │ ├── parameterize_constructor │ ├── Animals.java │ ├── Enclosure.java │ ├── Main.java │ ├── Zoo.java │ └── ZooManager.java │ ├── parameterize_method │ ├── Book.java │ ├── Library.java │ └── Main.java │ ├── primitivize_parameter │ ├── Main.java │ ├── Monster.java │ ├── MoveToMeetingPointCommand.java │ ├── Physics.java │ ├── Rendering.java │ └── Vector2.java │ ├── pull_up_feature │ ├── Main.java │ ├── Monster.java │ ├── Physics.java │ └── Rendering.java │ ├── push_down_dependency │ ├── Main.java │ ├── Monster.java │ ├── Physics.java │ └── Rendering.java │ ├── remove_duplication │ ├── Animal.java │ ├── Cat.java │ ├── Dog.java │ ├── Duplication.java │ └── Fish.java │ ├── spawn_class │ ├── Main.java │ └── Monster.java │ ├── spawn_method │ ├── Main.java │ └── Monster.java │ ├── subclass_and_override_method │ ├── AiEntity.java │ ├── Main.java │ ├── Monster.java │ ├── Physics.java │ ├── Rendering.java │ └── TestMonster.java │ ├── wrap_class │ ├── GameEntity.java │ ├── Main.java │ ├── Monster.java │ ├── Physics.java │ └── Rendering.java │ └── wrap_method │ ├── Main.java │ └── Monster.java └── test └── java └── com └── thoughtworks └── legacycode ├── adapt_parameter └── ReportGeneratorTest.java ├── break_out_method_object └── AiEntityTest.java ├── encapsulate_global_reference └── LibraryTest.java ├── expose_static_method └── LibraryTest.java ├── extract_and_override_call └── LibraryTest.java ├── parameterize_constructor └── ZooManagerTest.java ├── parameterize_method └── LibraryTest.java ├── primitivize_parameter └── MoveToMeetingPointCommandTest.java ├── pull_up_feature └── AiEntityTest.java ├── push_down_dependency └── AiEntityTest.java ├── spawn_class └── AiEntityTest.java ├── spawn_method └── AiEntityTest.java ├── subclass_and_override_method └── AiEntityTest.java ├── wrap_class └── AiEntityTest.java └── wrap_method └── AiEntityTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.class 3 | .gradle/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WorkingEffectivelyWithLegacyCode 2 | ================================ 3 | 4 | ## Why do we change software? 5 | 1. Add a feature - Add some behavior, hold existing behavior constant 6 | 1. Fix a bug - Change some behavior, hold other behavior constant 7 | 1. Improve design - Change code structure, hold existing behavior constant 8 | 1. Optimize resource usage - Change code to improve resource usage, hold existing behavior constant 9 | 10 | Detecting changes in existing behavior is important! 11 | 12 | # Legacy Code 13 | Legacy code is code without tests. -- *Michael Feathers* 14 | 15 | ## The Legacy Code Dilemma 16 | When we change code, we should have tests in place. To put tests in place, we often have to change code. 17 | 18 | ## The Legacy Code Change Algorithm 19 | 1. Identify Change Points 20 | 1. Find Test Points 21 | 1. Break Dependencies 22 | 1. Write Tests 23 | 1. Make Changes and Refactor 24 | 25 | ### How do I know that I'm not breaking anything? 26 | 27 | #### Rules 28 | 1. Hyper-aware Editing 29 | 1. Single Goal Editing 30 | 1. Preserve Signatures 31 | 1. Lean on the Compiler 32 | 1. Pair Programming 33 | 34 | #### Seams 35 | A seam is a place where you can alter behavior in your program without editing in that place. 36 | 37 | ##### Characterization Tests 38 | * *Characterizes* the actual behavior of the code. 39 | * Use white box testing to identify useful input values 40 | * Assert on the current actual results 41 | 42 | ##### Interception Points 43 | * An interception point is simply a point in your program where you can detect the effects of a particular change. Make this as close to your change points as you can. 44 | 45 | #### Process 46 | * Automated Refactoring to introduce basic seams and break dependencies 47 | * Cover with Characterization Tests and Regular Unit Tests 48 | * Introduce seams at the change and interception points using less safe refactorings (if needed) 49 | * TDD change 50 | 51 | ## Breaking Dependencies 52 | ### Sensing & Separation 53 | 54 | We break dependencies: 55 | * so we can *sense* when we can't access values our code computes 56 | * to *separate* when we can't even get a piece of code into a test harness to run. 57 | 58 | #### Sensing 59 | * *verify* 60 | * getters and non-private fields 61 | 62 | #### Separation 63 | * *when* 64 | * avoid using real resources 65 | * helps write maintainable tests 66 | 67 | ## Principles 68 | #### In order to make code better, we sometimes need to make some aspect of it worse. 69 | Testable & Clear > **Testable & Muddy** > **Untestable & Clear** > Untestable & Muddy 70 | 71 | _When you break dependencies in legacy code, you often have to suspend your sense of aesthetics a bit. Some 72 | dependencies break cleanly; others end up looking less than ideal from a design point of view. They are like the 73 | incision point in surgery: There might be a scar left in your code after your work, but everything beneath it can get better._ 74 | 75 | _If later you can cover the code around the point where you broke the dependencies, you can heal the scar too._ 76 | 77 | #### We have to carefully balance these priorities 78 | * New features 79 | * Design Improvements 80 | * Tests 81 | 82 | ## Practices 83 | ### Techniques for Breaking Dependencies 84 | #### Parameterize Constructor 85 | * Inject a dependency instead of leaving it internal to a class 86 | 87 | #### Parameterize Method 88 | * Inject a dependency instead of leaving it internal to a method 89 | 90 | #### Spawn Method 91 | * Introduce a method and TDD that 92 | 93 | #### Spawn Class 94 | * Introduce a class and TDD that 95 | 96 | #### Break out Method Object 97 | * Extract method you want to change into a new class and test that 98 | 99 | #### Subclass and Override Method 100 | * Test a subclass of your real class and override methods with dependencies 101 | 102 | #### Extract and Override Call 103 | * Extract tough dependency and override it then test child class 104 | 105 | #### Extract and Override Factory Method (No example yet) 106 | * Move constructor dependency to a method and override it 107 | 108 | #### Pull up Feature 109 | * Pull the parts of a class you want to test into a new abstract base class then test a child of that 110 | 111 | #### Push down Dependencies 112 | * Make current class abstract and push your dependencies into a child class. Test through a test child class. 113 | 114 | #### Expose Static Method 115 | * Change existing method to be static (if it can be). You can test without an instance 116 | 117 | #### Wrap Method 118 | * Introduce a method that contains an existing method and a call to you new method 119 | 120 | #### Wrap Class aka Decorator 121 | * Wrap your hard to test class with a Decorator and TDD the decorator 122 | 123 | #### Adapt Parameter 124 | * Use adapter pattern on tough dependency 125 | 126 | #### Encapsulate Global Reference 127 | * Introduce new class that holds your global which it exposes with a getter 128 | 129 | #### Introduce Instance Delegator (No example yet) 130 | * Introduce a new class that contains related global methods 131 | 132 | #### Primitivize Parameter 133 | * Pass the values from an object instead of the object 134 | 135 | #### Introduce Static Setter (No example yet) 136 | * Add a setInstance to your existing Singleton (**Danger!**) 137 | 138 | ### Supporting Concepts 139 | 140 | #### Scratch Refactoring 141 | * Refactor the code to understand it better, *then throw it away*. 142 | * Use only automated refactorings, *then check it in*. <- Bill's version 143 | 144 | #### Removing Duplication 145 | * Use automated refactorings to make different code blocks identical 146 | * Extract method or variable (IDE does the rest) 147 | * Do example 148 | 149 | #### Monster Methods 150 | * Bulleted Method - indentation is not the most obvious problem 151 | * Snarled Method - indentation makes you dizzy 152 | 153 | #### Command/Query Separation 154 | * Modify state or report state. Getters should be idempotent 155 | 156 | 157 | -------------------------------------------------------------------------------- /WorkingEffectivelyWithLegacyCodeExamples.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | sourceCompatibility = 1.7 4 | version = '1.0' 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | compile 'joda-time:joda-time:2.7' 12 | testCompile 'junit:junit:4.11' 13 | testCompile 'org.mockito:mockito-core:1.10.19' 14 | testCompile 'org.hamcrest:hamcrest-core:1.3' 15 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/WorkingEffectivelyWithLegacyCode/07d97efded14c73f18f50767b95964ccb5b3f6a7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Oct 07 10:58:45 CDT 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /out/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThoughtWorksInc/WorkingEffectivelyWithLegacyCode/07d97efded14c73f18f50767b95964ccb5b3f6a7/out/.gitignore -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/adapt_parameter/Main.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.adapt_parameter; 2 | 3 | import java.io.Console; 4 | import java.io.InputStream; 5 | 6 | public class Main { 7 | public static void main(String[] args) { 8 | final ReportGenerator reportGenerator = new ReportGenerator(); 9 | final Console console = System.console(); 10 | reportGenerator.reportToConsole(console); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/adapt_parameter/ReportGenerator.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.adapt_parameter; 2 | 3 | import java.io.Console; 4 | 5 | public class ReportGenerator { 6 | 7 | // Change the console parameter to your new adapter class 8 | public void reportToConsole(Console console) { 9 | final String userInput = console.readLine(); 10 | console.printf(userInput); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/break_out_method_object/Main.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.break_out_method_object; 2 | 3 | public class Main { 4 | public static void main(String[] args) { 5 | new Monster(new Physics(), new Rendering(), 10, "Angry").processAi(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/break_out_method_object/Monster.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.break_out_method_object; 2 | 3 | public class Monster { 4 | 5 | private Physics physics; 6 | private Rendering rendering; 7 | private Integer hitPoints; 8 | private String state; 9 | // many more instance variables 10 | 11 | 12 | public Monster(Physics physics, Rendering rendering, Integer hitPoints, String state) { 13 | this.physics = physics; 14 | this.rendering = rendering; 15 | this.hitPoints = hitPoints; 16 | this.state = state; 17 | this.physics.add(this); 18 | this.rendering.render(this); 19 | } 20 | 21 | // Move this method to a new class and test that method 22 | public void processAi(){ 23 | if (hitPoints > 5){ 24 | if (state.equals("Angry")){ 25 | moveToNearestEnemy(); 26 | attack(); 27 | } else if (state.equals("Afraid")){ 28 | moveAwayFromNearestEnemy(); 29 | } 30 | } else { 31 | rest(); 32 | } 33 | } 34 | 35 | private void rest() { 36 | // lots of code here 37 | // none of it uses physics or rendering 38 | } 39 | 40 | private void moveAwayFromNearestEnemy() { 41 | // lots of code here 42 | // none of it uses physics or rendering 43 | } 44 | 45 | private void attack() { 46 | // lots of code here 47 | // none of it uses physics or rendering 48 | } 49 | 50 | private void moveToNearestEnemy() { 51 | // lots of code here 52 | // none of it uses physics or rendering 53 | } 54 | 55 | public void state(String newState) { 56 | state = newState; 57 | } 58 | 59 | public Integer hitPoints() { 60 | return hitPoints; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/break_out_method_object/Physics.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.break_out_method_object; 2 | 3 | public final class Physics { 4 | public static Physics getInstance() { 5 | return null; 6 | } 7 | 8 | public void add(Monster monster) { 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/break_out_method_object/Rendering.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.break_out_method_object; 2 | 3 | public final class Rendering { 4 | public void render(Monster monster) { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/encapsulate_global_reference/Book.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.encapsulate_global_reference; 2 | 3 | /** 4 | * Created by ThoughtWorker on 4/7/14. 5 | */ 6 | public class Book { 7 | private boolean overDue=false; 8 | private String name; 9 | 10 | public Book(String name) { 11 | 12 | this.name = name; 13 | } 14 | 15 | public boolean isOverDue() { 16 | return overDue; 17 | } 18 | 19 | public String getName() { 20 | return name; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/encapsulate_global_reference/Library.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.encapsulate_global_reference; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Library { 7 | public void listBooks() { 8 | for (Book book : Main.BookList) { 9 | System.out.println(book.getName()); 10 | } 11 | } 12 | 13 | public void listOverdueBooks() { 14 | List overdueBooks = new ArrayList(); 15 | for (Book book : Main.BookList) { 16 | if (book.isOverDue()){ 17 | overdueBooks.add(book); 18 | } 19 | } 20 | for (Book overdueBook : overdueBooks) { 21 | System.out.println(overdueBook.getName()); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/encapsulate_global_reference/Main.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.encapsulate_global_reference; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | public class Main { 7 | public static List BookList = Arrays.asList(new Book("The Two Towers"), new Book("House of Leaves")); 8 | 9 | public static void main(String[] args) { 10 | Library library = new Library(); 11 | library.listBooks(); 12 | library.listOverdueBooks(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/exercise/Defragger.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.exercise; 2 | 3 | public class Defragger { 4 | private String[] p; 5 | 6 | public Defragger(String[] p) { 7 | this.p = p; 8 | } 9 | 10 | public boolean canDefrag(Partition p) { 11 | for(String n : this.p){ 12 | if (n.equals(p)){ 13 | return true; 14 | } 15 | } 16 | return false; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/exercise/DiskUtils.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.exercise; 2 | 3 | import java.io.File; 4 | 5 | public class DiskUtils { 6 | private static DiskUtils instance = null; 7 | 8 | public void format(String input) { 9 | // code that formats a physical hard drive partition 10 | 11 | } 12 | 13 | public static DiskUtils getInstance() { 14 | if (instance == null){ 15 | instance = new DiskUtils(); 16 | } 17 | return instance; 18 | } 19 | 20 | public File getFile(String fn) { 21 | return new File(fn); 22 | } 23 | 24 | public void useDefragger(FileManager fm, Defragger defragger) throws PartitionDoesNotExistException { 25 | String pn = fm.input("Enter partition name"); 26 | if (defragger.canDefrag(getPartition(pn))){ 27 | 28 | } 29 | 30 | } 31 | 32 | private Partition getPartition(String n) throws PartitionDoesNotExistException { 33 | return new Partition(n); 34 | } 35 | 36 | public boolean canFindPartition(String n) { 37 | // Looks for partition on disk and returns true if it exists 38 | return false; 39 | } 40 | public boolean canFindFile(String n) { 41 | // Looks for file on disk and returns true if it exists 42 | return false; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/exercise/FileManager.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.exercise; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.io.InputStreamReader; 7 | 8 | public class FileManager { 9 | private static Defragger df; 10 | private String input = null; 11 | private BufferedReader br = null; 12 | 13 | public static void run() throws PartitionDoesNotExistException { 14 | FileManager fm = new FileManager(); 15 | while(!fm.input("Enter disk command").equals("quit")){ 16 | String input1 = fm.getInput(); 17 | if(input1.equals("format")) { 18 | DiskUtils.getInstance().format(fm.input("Enter name of drive to format")); 19 | } 20 | else if(input1.equals("print")) { 21 | String fn = fm.input("Enter file name"); 22 | File file = DiskUtils.getInstance().getFile(fn); 23 | FilePrinter fp = new FilePrinter(file); 24 | fp.print(); 25 | } 26 | else if (input1.equals("defrag")){ 27 | DiskUtils.getInstance().useDefragger(fm, df); 28 | } 29 | } 30 | } 31 | 32 | public String getInput() { 33 | if (input == null){ 34 | input = input(); 35 | } 36 | return input; 37 | } 38 | 39 | private String input() { 40 | if (br == null){ 41 | br = new BufferedReader(new InputStreamReader(System.in)); 42 | } 43 | try { 44 | return br.readLine(); 45 | } catch (IOException e) { 46 | e.printStackTrace(); 47 | } 48 | return ""; 49 | } 50 | 51 | public String input(String prompt) { 52 | System.out.println(prompt); 53 | if (br == null){ 54 | br = new BufferedReader(new InputStreamReader(System.in)); 55 | } 56 | try { 57 | return br.readLine(); 58 | } catch (IOException e) { 59 | e.printStackTrace(); 60 | } 61 | return ""; 62 | } 63 | 64 | public static void init(String[] partitions) { 65 | df = new Defragger(partitions); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/exercise/FilePrinter.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.exercise; 2 | 3 | import java.io.File; 4 | 5 | public class FilePrinter { 6 | public FilePrinter(File file) { 7 | 8 | } 9 | 10 | public void print() { 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/exercise/Main.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.exercise; 2 | 3 | public class Main { 4 | 5 | public static void main(String[] args) throws PartitionDoesNotExistException { 6 | String[] partitions = {"Partition1", "Partition2"}; 7 | FileManager.init(partitions); 8 | FileManager.run(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/exercise/Partition.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.exercise; 2 | 3 | public class Partition { 4 | public Partition(String n) throws PartitionDoesNotExistException { 5 | if (!DiskUtils.getInstance().canFindPartition(n)){ 6 | throw new PartitionDoesNotExistException(); 7 | } 8 | // initialize partition 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/exercise/PartitionDoesNotExistException.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.exercise; 2 | 3 | public class PartitionDoesNotExistException extends Exception { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/expose_static_method/Book.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.expose_static_method; 2 | 3 | public class Book { 4 | private boolean overDue; 5 | private String name; 6 | 7 | public Book(String name, boolean overdue) { 8 | this.name = name; 9 | this.overDue = overdue; 10 | } 11 | 12 | public boolean isOverDue() { 13 | return overDue; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/expose_static_method/Library.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.expose_static_method; 2 | 3 | import java.io.Console; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | public class Library { 8 | 9 | private final String libraryName; 10 | 11 | public Library(Console console) { 12 | // We need to avoid calling this line in our tests. So we need to not call the constructor 13 | libraryName = console.readLine(); 14 | } 15 | 16 | public void printBooks(List books) { 17 | System.out.println(libraryName); 18 | for (Book book : books) { 19 | System.out.println(book); 20 | } 21 | } 22 | 23 | // Make this static so you can test it without instantiating Library 24 | public List overdueBooks(List bookList) { 25 | List overdueBooks = new ArrayList(); 26 | for (Book book : bookList) { 27 | if (book.isOverDue()){ 28 | overdueBooks.add(book); 29 | } 30 | } 31 | return overdueBooks; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/expose_static_method/Main.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.expose_static_method; 2 | 3 | import java.util.List; 4 | 5 | import static java.util.Arrays.asList; 6 | 7 | public class Main { 8 | public static void main(String[] args) { 9 | List bookList = asList(new Book("The Two Towers", true), new Book("House of Leaves", true)); 10 | final Library library = new Library(System.console()); 11 | library.printBooks(bookList); 12 | final List overdueBooks = library.overdueBooks(bookList); 13 | library.printBooks(overdueBooks); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/extract_and_override_call/Book.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.extract_and_override_call; 2 | 3 | public class Book { 4 | private String name; 5 | 6 | public Book(String name) { 7 | this.name = name; 8 | } 9 | 10 | public String getName() { 11 | return name; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/extract_and_override_call/Library.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.extract_and_override_call; 2 | 3 | import java.io.PrintStream; 4 | import java.util.List; 5 | 6 | public class Library { 7 | private PrintStream printStream; 8 | 9 | public Library(PrintStream printStream) { 10 | this.printStream = printStream; 11 | } 12 | 13 | public void printBooks(List books) { 14 | 15 | // Extract this line into a protected method and override it in an new subclass 16 | final String libraryName = System.console().readLine(); 17 | 18 | printStream.println(libraryName); 19 | for (Book book : books) { 20 | printStream.println(book.getName()); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/extract_and_override_call/Main.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.extract_and_override_call; 2 | 3 | import java.util.List; 4 | 5 | import static java.util.Arrays.asList; 6 | 7 | public class Main { 8 | public static void main(String[] args) { 9 | List bookList = asList(new Book("The Two Towers"), new Book("House of Leaves")); 10 | final Library library = new Library(System.out); 11 | library.printBooks(bookList); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/parameterize_constructor/Animals.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.parameterize_constructor; 2 | 3 | import com.thoughtworks.legacycode.remove_duplication.Animal; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class Animals { 9 | private static List wild; 10 | 11 | public static List wild() { 12 | if (wild == null){ 13 | wild = new ArrayList(); 14 | } 15 | return wild; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/parameterize_constructor/Enclosure.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.parameterize_constructor; 2 | 3 | import com.thoughtworks.legacycode.remove_duplication.Animal; 4 | 5 | public class Enclosure { 6 | public void add(Animal animal) { 7 | 8 | } 9 | 10 | public boolean contains(Animal animal) { 11 | return false; 12 | } 13 | 14 | public boolean isEmpty() { 15 | return false; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/parameterize_constructor/Main.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.parameterize_constructor; 2 | 3 | public class Main { 4 | public static void main(String[] args) { 5 | final ZooManager zooManager = new ZooManager(); 6 | zooManager.moveWildAnimalsToEnclosures(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/parameterize_constructor/Zoo.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.parameterize_constructor; 2 | 3 | public class Zoo { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/parameterize_constructor/ZooManager.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.parameterize_constructor; 2 | 3 | import com.thoughtworks.legacycode.remove_duplication.Animal; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class ZooManager { 9 | private Zoo zoo; 10 | private List enclosures; 11 | 12 | public ZooManager() { 13 | zoo = new Zoo(); 14 | } 15 | 16 | public int numberOfEnclosures(){ 17 | if (enclosures == null){ 18 | enclosures = new ArrayList(); 19 | } 20 | return enclosures.size(); 21 | } 22 | 23 | public void addEnclosure(Enclosure enclosure){ 24 | if (enclosures == null){ 25 | enclosures = new ArrayList(); 26 | } 27 | enclosures.add(enclosure); 28 | } 29 | 30 | public void moveWildAnimalsToEnclosures(){ 31 | for (Animal animal : Animals.wild()) { 32 | findEnclosureFor(animal).add(animal); 33 | } 34 | Animals.wild().clear(); 35 | } 36 | 37 | public Zoo getZoo() { 38 | return zoo; 39 | } 40 | 41 | private Enclosure findEnclosureFor(Animal animal) { 42 | if (enclosures == null){ 43 | enclosures = new ArrayList(); 44 | } 45 | for (Enclosure enclosure : enclosures) { 46 | if (enclosure.contains(animal)){ 47 | return enclosure; 48 | } 49 | } 50 | for (Enclosure enclosure : enclosures) { 51 | if (enclosure.isEmpty()){ 52 | return enclosure; 53 | } 54 | } 55 | return null; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/parameterize_method/Book.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.parameterize_method; 2 | 3 | public class Book { 4 | private boolean overDue=false; 5 | private String name; 6 | 7 | public Book(String name) { 8 | 9 | this.name = name; 10 | } 11 | 12 | public boolean isOverDue() { 13 | return overDue; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/parameterize_method/Library.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.parameterize_method; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.io.PrintStream; 7 | import java.util.List; 8 | 9 | public class Library { 10 | private PrintStream printStream; 11 | 12 | public Library(PrintStream printStream) { 13 | this.printStream = printStream; 14 | } 15 | 16 | public void printBooks(List books) throws IOException { 17 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); 18 | final String libraryName = bufferedReader.readLine(); 19 | printStream.println(libraryName); 20 | for (Book book : books) { 21 | printStream.println(book.getName()); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/parameterize_method/Main.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.parameterize_method; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | 6 | import static java.util.Arrays.asList; 7 | 8 | public class Main { 9 | public static void main(String[] args) throws IOException { 10 | List bookList = asList(new Book("The Two Towers"), new Book("House of Leaves")); 11 | final Library library = new Library(System.out); 12 | library.printBooks(bookList); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/primitivize_parameter/Main.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.primitivize_parameter; 2 | 3 | public class Main { 4 | public static void main(String[] args) { 5 | final Monster monster1 = new Monster(new Vector2(0, 0)); 6 | final Monster monster2 = new Monster(new Vector2(10, 10)); 7 | 8 | MoveToMeetingPointCommand command = new MoveToMeetingPointCommand(monster1, monster2); 9 | command.execute(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/primitivize_parameter/Monster.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.primitivize_parameter; 2 | 3 | public class Monster { 4 | 5 | private Physics physics = Physics.getInstance(); 6 | private Rendering rendering = new Rendering(); 7 | private Vector2 position; 8 | 9 | 10 | public Monster(Vector2 position) { 11 | this.position = position; 12 | physics.add(this); 13 | rendering.render(this); 14 | } 15 | 16 | public Vector2 position() { 17 | return position; 18 | } 19 | 20 | public void moveTo(Vector2 destination) { 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/primitivize_parameter/MoveToMeetingPointCommand.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.primitivize_parameter; 2 | 3 | public class MoveToMeetingPointCommand { 4 | private final Monster monster1; 5 | private final Monster monster2; 6 | 7 | public MoveToMeetingPointCommand(Monster monster1, Monster monster2) { 8 | this.monster1 = monster1; 9 | this.monster2 = monster2; 10 | } 11 | 12 | public void execute() { 13 | Vector2 goalLocation = findMidpoint(monster1, monster2); 14 | monster1.moveTo(goalLocation); 15 | monster2.moveTo(goalLocation); 16 | } 17 | 18 | private Vector2 findMidpoint(Monster monster1, Monster monster2) { 19 | return monster1.position().average(monster2.position()); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/primitivize_parameter/Physics.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.primitivize_parameter; 2 | 3 | public class Physics { 4 | public static Physics getInstance() { 5 | return null; 6 | } 7 | 8 | public void add(Monster monster) { 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/primitivize_parameter/Rendering.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.primitivize_parameter; 2 | 3 | public class Rendering { 4 | public void render(Monster monster) { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/primitivize_parameter/Vector2.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.primitivize_parameter; 2 | 3 | 4 | public class Vector2 { 5 | private double x; 6 | private double y; 7 | 8 | public Vector2(double x, double y) { 9 | this.x = x; 10 | this.y = y; 11 | } 12 | 13 | public Vector2 average(Vector2 that) { 14 | return new Vector2(average(this.x, that.x), average(this.y, that.y)); 15 | } 16 | 17 | private double average(double number1, double number2) { 18 | return (number1 + number2)/2; 19 | } 20 | 21 | public Vector2 minus(Vector2 that) { 22 | return null; 23 | } 24 | 25 | public Vector2 add(Vector2 that) { 26 | return null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/pull_up_feature/Main.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.pull_up_feature; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | Monster monster = new Monster(10, "Angry", new Vector2(0, 0)); 8 | monster.processAi();monster.draw(); 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/pull_up_feature/Monster.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.pull_up_feature; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Monster { 6 | 7 | private Physics physics = Physics.getInstance(); 8 | private Rendering rendering = new Rendering(); 9 | private Integer hitPoints; 10 | private String state; 11 | private Vector2 position; 12 | private Integer damage = 2; 13 | // many more instance variables 14 | 15 | 16 | public Monster(Integer hitPoints, String state, Vector2 position) { 17 | this.hitPoints = hitPoints; 18 | this.state = state; 19 | this.position = position; 20 | physics.add(this); 21 | rendering.render(this); 22 | } 23 | 24 | // Pull this up into an abstract base class. Make all other methods abstract. 25 | public void processAi(){ 26 | if (hitPoints > 5){ 27 | Monster nearestEnemy = findNearestEnemy(); 28 | if (state.equals("Angry")){ 29 | moveTo(nearestEnemy); 30 | attack(nearestEnemy); 31 | } else if (state.equals("Afraid")){ 32 | moveAwayFrom(nearestEnemy); 33 | } 34 | } else { 35 | rest(); 36 | } 37 | } 38 | 39 | // Stub this out in new child class so it returns a canned result that you can 40 | public Monster findNearestEnemy() { 41 | return physics.findNearestEntityTo(position); 42 | } 43 | 44 | public void rest() { 45 | hitPoints++; 46 | if (hitPoints > 5){ 47 | state = "Angry"; 48 | } 49 | } 50 | 51 | public void moveAwayFrom(Monster target) { 52 | Vector2 offsetFromMonsterToMe = position.minus(target.position); 53 | moveTo(position.add(offsetFromMonsterToMe)); 54 | } 55 | 56 | public void attack(Monster target) { 57 | target.hitPoints -= damage; 58 | } 59 | 60 | public void moveTo(Monster target) { 61 | moveTo(target.position); 62 | } 63 | 64 | public void draw(){ 65 | rendering.render(this); 66 | } 67 | 68 | public void moveTo(Vector2 position){ 69 | this.position = position; 70 | physics.move(this, position); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/pull_up_feature/Physics.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.pull_up_feature; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Physics { 6 | public static Physics getInstance() { 7 | return null; 8 | } 9 | 10 | public void add(Monster monster) { 11 | 12 | } 13 | 14 | public Monster findNearestEntityTo(Vector2 position) { 15 | return null; 16 | } 17 | 18 | public void move(Monster entity, Vector2 position) { 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/pull_up_feature/Rendering.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.pull_up_feature; 2 | 3 | public class Rendering { 4 | public void render(Monster monster) { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/push_down_dependency/Main.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.push_down_dependency; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | Monster monster = new Monster(10, "Angry", new Vector2(0, 0)); 8 | monster.processAi(); 9 | monster.draw(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/push_down_dependency/Monster.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.push_down_dependency; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Monster { 6 | 7 | // Push Physics and Rendering down 8 | private Physics physics = Physics.getInstance(); 9 | private Rendering rendering = new Rendering(); 10 | 11 | private Integer hitPoints; 12 | private String state; 13 | private Vector2 position; 14 | private Integer damage = 2; 15 | // many more instance variables 16 | 17 | 18 | public Monster(Integer hitPoints, String state, Vector2 position) { 19 | this.hitPoints = hitPoints; 20 | this.state = state; 21 | this.position = position; 22 | // Push Physics and Rendering down 23 | physics.add(this); 24 | rendering.render(this); 25 | } 26 | 27 | public void processAi(){ 28 | if (hitPoints > 5){ 29 | Monster nearestEnemy = findNearestEnemy(); 30 | if (state.equals("Angry")){ 31 | moveTo(nearestEnemy); 32 | attack(nearestEnemy); 33 | } else if (state.equals("Afraid")){ 34 | moveAwayFrom(nearestEnemy); 35 | } 36 | } else { 37 | rest(); 38 | } 39 | } 40 | 41 | // Push this down 42 | public Monster findNearestEnemy() { 43 | return physics.findNearestEntityTo(position); 44 | } 45 | 46 | public void rest() { 47 | hitPoints++; 48 | if (hitPoints > 5){ 49 | state = "Angry"; 50 | } 51 | } 52 | 53 | public void moveAwayFrom(Monster target) { 54 | Vector2 offsetFromMonsterToMe = position.minus(target.position); 55 | moveTo(position.add(offsetFromMonsterToMe)); 56 | } 57 | 58 | public void attack(Monster target) { 59 | target.hitPoints -= damage; 60 | } 61 | 62 | public void moveTo(Monster target) { 63 | moveTo(target.position); 64 | } 65 | 66 | // Push this down 67 | public void draw(){ 68 | rendering.render(this); 69 | } 70 | 71 | // Push this down 72 | public void moveTo(Vector2 position){ 73 | this.position = position; 74 | physics.move(this, position); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/push_down_dependency/Physics.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.push_down_dependency; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Physics { 6 | public static Physics getInstance() { 7 | return null; 8 | } 9 | 10 | public void add(Monster monster) { 11 | 12 | } 13 | 14 | public Monster findNearestEntityTo(Vector2 position) { 15 | return null; 16 | } 17 | 18 | public void move(Monster entity, Vector2 position) { 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/push_down_dependency/Rendering.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.push_down_dependency; 2 | 3 | public class Rendering { 4 | public void render(Monster monster) { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/remove_duplication/Animal.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.remove_duplication; 2 | 3 | public interface Animal { 4 | boolean likes(String thing); 5 | 6 | String name(); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/remove_duplication/Cat.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.remove_duplication; 2 | 3 | public class Cat implements Animal{ 4 | @Override 5 | public boolean likes(String thing) { 6 | return false; 7 | } 8 | 9 | @Override 10 | public String name() { 11 | return null; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/remove_duplication/Dog.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.remove_duplication; 2 | 3 | public class Dog implements Animal { 4 | @Override 5 | public boolean likes(String thing) { 6 | return false; 7 | } 8 | 9 | @Override 10 | public String name() { 11 | return null; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/remove_duplication/Duplication.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.remove_duplication; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | 6 | public class Duplication { 7 | public static void main(String[] args) { 8 | ArrayList animals = new ArrayList(); 9 | Animal cat = new Cat(); 10 | Animal dog = new Dog(); 11 | Animal fish = new Fish(); 12 | animals.addAll(Arrays.asList(cat, dog, fish)); 13 | 14 | Integer command = 9; 15 | switch(command){ 16 | case 1: 17 | for (Animal animal : animals) { 18 | if (animal.likes("water")){ 19 | System.out.println(animal.name() + "likes water."); 20 | } 21 | } 22 | break; 23 | case 2: 24 | for (Animal animal : animals) { 25 | if (animal.likes("running")){ 26 | System.out.println(animal.name() + "likes running."); 27 | } 28 | } 29 | break; 30 | case 3: 31 | int indexOfCat = animals.lastIndexOf(cat); 32 | animals.add(indexOfCat, new Cat()); 33 | break; 34 | case 4: 35 | animals.add(animals.lastIndexOf(dog), new Dog()); 36 | break; 37 | case 5: 38 | animals.add(animals.lastIndexOf(fish), new Fish()); 39 | break; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/remove_duplication/Fish.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.remove_duplication; 2 | 3 | public class Fish implements Animal{ 4 | @Override 5 | public boolean likes(String thing) { 6 | return false; 7 | } 8 | 9 | @Override 10 | public String name() { 11 | return null; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/spawn_class/Main.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.spawn_class; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | Monster monster = new Monster(10, "Angry", new Vector2(0, 0)); 8 | monster.processAi(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/spawn_class/Monster.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.spawn_class; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Monster { 6 | 7 | private Integer hitPoints; 8 | private String state; 9 | private Vector2 position; 10 | private Integer damage = 2; 11 | 12 | 13 | public Monster(Integer hitPoints, String state, Vector2 position) { 14 | this.hitPoints = hitPoints; 15 | this.state = state; 16 | this.position = position; 17 | } 18 | 19 | public void processAi(){ 20 | // Create an instance of a new class here and call a method on it. 21 | if (hitPoints > 5){ 22 | String enemyName = System.console().readLine(); 23 | Monster enemy = findEnemyByName(enemyName); 24 | if (state.equals("Angry")){ 25 | moveTo(enemy); 26 | attack(enemy); 27 | } else if (state.equals("Afraid")){ 28 | moveAwayFrom(enemy); 29 | } 30 | } else { 31 | rest(); 32 | } 33 | } 34 | 35 | private Monster findEnemyByName(String enemyName) { 36 | return null; 37 | } 38 | 39 | public void rest() { 40 | hitPoints++; 41 | if (hitPoints > 5){ 42 | state = "Angry"; 43 | } 44 | } 45 | 46 | public void moveAwayFrom(Monster target) { 47 | Vector2 offsetFromMonsterToMe = position.minus(target.position); 48 | moveTo(position.add(offsetFromMonsterToMe)); 49 | } 50 | 51 | public void attack(Monster target) { 52 | target.hitPoints -= damage; 53 | } 54 | 55 | public void moveTo(Monster target) { 56 | moveTo(target.position); 57 | } 58 | 59 | public void moveTo(Vector2 position){ 60 | this.position = position; 61 | } 62 | 63 | public String state() { 64 | return state; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/spawn_method/Main.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.spawn_method; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | new Monster(10, "Angry", new Vector2(0, 0)).processAi(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/spawn_method/Monster.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.spawn_method; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Monster { 6 | 7 | private Integer hitPoints; 8 | private String state; 9 | private Vector2 position; 10 | private Integer damage = 2; 11 | 12 | 13 | public Monster(Integer hitPoints, String state, Vector2 position) { 14 | this.hitPoints = hitPoints; 15 | this.state = state; 16 | this.position = position; 17 | } 18 | 19 | public void processAi(){ 20 | // Call new method here. It should be public so you can test it. 21 | if (hitPoints > 5){ 22 | String enemyName = System.console().readLine(); 23 | Monster enemy = findEnemyByName(enemyName); 24 | if (state.equals("Angry")){ 25 | moveTo(enemy); 26 | attack(enemy); 27 | } else if (state.equals("Afraid")){ 28 | moveAwayFrom(enemy); 29 | } 30 | } else { 31 | rest(); 32 | } 33 | } 34 | 35 | public Monster findEnemyByName(String enemyName) { 36 | return null; 37 | } 38 | 39 | public void rest() { 40 | hitPoints++; 41 | if (hitPoints > 5){ 42 | state = "Angry"; 43 | } 44 | } 45 | 46 | public void moveAwayFrom(Monster target) { 47 | Vector2 offsetFromMonsterToMe = position.minus(target.position); 48 | moveTo(position.add(offsetFromMonsterToMe)); 49 | } 50 | 51 | public void attack(Monster target) { 52 | target.hitPoints -= damage; 53 | } 54 | 55 | public void moveTo(Monster target) { 56 | moveTo(target.position); 57 | } 58 | 59 | public void moveTo(Vector2 position){ 60 | this.position = position; 61 | } 62 | 63 | public String state() { 64 | return state; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/subclass_and_override_method/AiEntity.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.subclass_and_override_method; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public abstract class AiEntity { 6 | 7 | private Integer hitPoints; 8 | private String state; 9 | protected Vector2 position; 10 | private Integer damage = 2; 11 | // many more instance variables 12 | 13 | 14 | public AiEntity(Integer hitPoints, String state, Vector2 position) { 15 | this.hitPoints = hitPoints; 16 | this.state = state; 17 | this.position = position; 18 | } 19 | 20 | public void processAi(){ 21 | if (hitPoints > 5){ 22 | AiEntity nearestEnemy = findNearestEnemy(); 23 | if (state.equals("Angry")){ 24 | moveTo(nearestEnemy); 25 | attack(nearestEnemy); 26 | } else if (state.equals("Afraid")){ 27 | moveAwayFrom(nearestEnemy); 28 | } 29 | } else { 30 | rest(); 31 | } 32 | } 33 | 34 | // Override this method in new subclass because Physics is scary 35 | // We can also stub this to return the enemy that we will use for our assertion 36 | public abstract AiEntity findNearestEnemy(); 37 | 38 | public void rest() { 39 | hitPoints++; 40 | if (hitPoints > 5){ 41 | state = "Angry"; 42 | } 43 | } 44 | 45 | public void moveAwayFrom(AiEntity target) { 46 | Vector2 offsetFromMonsterToMe = position.minus(target.position); 47 | moveTo(position.add(offsetFromMonsterToMe)); 48 | } 49 | 50 | public void attack(AiEntity target) { 51 | target.hitPoints -= damage; 52 | } 53 | 54 | public abstract void moveTo(AiEntity target); 55 | 56 | public abstract void draw(); 57 | 58 | // Override this method in new subclass because Physics is scary 59 | public abstract void moveTo(Vector2 position); 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/subclass_and_override_method/Main.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.subclass_and_override_method; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | // AiEntity aiEntity = new Monster(10, "Angry", new Vector2(0, 0)); 8 | // aiEntity.processAi(); 9 | // aiEntity.draw(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/subclass_and_override_method/Monster.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.subclass_and_override_method; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Monster extends AiEntity { 6 | private Physics physics; 7 | private Rendering rendering; 8 | 9 | public Monster(Physics physics, Rendering rendering, Integer hitPoints, String state, Vector2 position) { 10 | super(hitPoints, state, position); 11 | this.physics = physics; 12 | this.rendering = rendering; 13 | this.physics.add(this); 14 | this.rendering.render(this); 15 | } 16 | 17 | public void moveTo(Vector2 position){ 18 | this.position = position; 19 | physics.move(this, position); 20 | } 21 | 22 | public void draw(){ 23 | rendering.render(this); 24 | } 25 | 26 | public AiEntity findNearestEnemy() { 27 | return physics.findNearestEntityTo(position); 28 | } 29 | 30 | public void moveTo(AiEntity target) { 31 | moveTo(target.position); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/subclass_and_override_method/Physics.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.subclass_and_override_method; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Physics { 6 | public static Physics getInstance() { 7 | return null; 8 | } 9 | 10 | public void add(AiEntity aiEntity) { 11 | 12 | } 13 | 14 | public AiEntity findNearestEntityTo(Vector2 position) { 15 | return null; 16 | } 17 | 18 | public void move(AiEntity entity, Vector2 position) { 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/subclass_and_override_method/Rendering.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.subclass_and_override_method; 2 | 3 | public class Rendering { 4 | public void render(AiEntity aiEntity) { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/subclass_and_override_method/TestMonster.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.subclass_and_override_method; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class TestMonster extends AiEntity { 6 | public AiEntity nearestEnemy; 7 | public AiEntity enemyWeMovedTo; 8 | 9 | public TestMonster(Integer hitPoints, String state, Vector2 position) { 10 | super(hitPoints, state, position); 11 | } 12 | 13 | @Override 14 | public AiEntity findNearestEnemy() { 15 | return nearestEnemy; 16 | } 17 | 18 | @Override 19 | public void moveTo(AiEntity target) { 20 | enemyWeMovedTo = target; 21 | } 22 | 23 | @Override 24 | public void draw() { 25 | 26 | } 27 | 28 | @Override 29 | public void moveTo(Vector2 position) { 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/wrap_class/GameEntity.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.wrap_class; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public interface GameEntity { 6 | void processAi(); 7 | 8 | void moveTo(GameEntity target); 9 | 10 | void draw(); 11 | 12 | void moveTo(Vector2 position); 13 | 14 | Vector2 position(); 15 | 16 | void decreaseHitPoints(Integer damage); 17 | 18 | String state(); 19 | 20 | void position(Vector2 position); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/wrap_class/Main.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.wrap_class; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | GameEntity monster = new Monster(10, "Angry", new Vector2(0, 0)); 8 | monster.processAi(); 9 | monster.draw(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/wrap_class/Monster.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.wrap_class; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Monster implements GameEntity{ 6 | 7 | private Physics physics = Physics.getInstance(); 8 | private Rendering rendering = new Rendering(); 9 | private Integer hitPoints; 10 | private String state; 11 | private Vector2 position; 12 | private Integer damage = 2; 13 | // many more instance variables 14 | 15 | 16 | public Monster(Integer hitPoints, String state, Vector2 position) { 17 | this.hitPoints = hitPoints; 18 | this.state = state; 19 | this.position = position; 20 | physics.add(this); 21 | rendering.render(this); 22 | } 23 | 24 | @Override 25 | public void processAi(){ 26 | if (hitPoints > 5){ 27 | String nameOfEnemy = System.console().readLine(); 28 | GameEntity enemy = findEnemyByName(nameOfEnemy); 29 | if (state.equals("Angry")){ 30 | moveTo(enemy); 31 | attack(enemy); 32 | } else if (state.equals("Afraid")){ 33 | moveAwayFrom(enemy); 34 | } 35 | } else { 36 | rest(); 37 | } 38 | } 39 | 40 | private GameEntity findEnemyByName(String nameOfEnemy) { 41 | return null; 42 | } 43 | 44 | public void rest() { 45 | hitPoints++; 46 | if (hitPoints > 5){ 47 | state = "Angry"; 48 | } 49 | } 50 | 51 | public void moveAwayFrom(GameEntity target) { 52 | Vector2 offsetFromMonsterToMe = position.minus(target.position()); 53 | moveTo(position.add(offsetFromMonsterToMe)); 54 | } 55 | 56 | public void attack(GameEntity target) { 57 | target.decreaseHitPoints(damage); 58 | } 59 | 60 | @Override 61 | public void moveTo(GameEntity target) { 62 | moveTo(target.position()); 63 | } 64 | 65 | @Override 66 | public void draw(){ 67 | rendering.render(this); 68 | } 69 | 70 | @Override 71 | public void moveTo(Vector2 position){ 72 | this.position = position; 73 | physics.move(this, position); 74 | } 75 | 76 | @Override 77 | public Vector2 position() { 78 | return position; 79 | } 80 | 81 | @Override 82 | public void decreaseHitPoints(Integer damage) { 83 | hitPoints -= damage; 84 | } 85 | 86 | @Override 87 | public String state() { 88 | return state; 89 | } 90 | 91 | @Override 92 | public void position(Vector2 position) { 93 | this.position = position; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/wrap_class/Physics.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.wrap_class; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Physics { 6 | private Monster monster; 7 | 8 | public static Physics getInstance() { 9 | return null; 10 | } 11 | 12 | public void add(Monster monster) { 13 | this.monster = monster; 14 | } 15 | 16 | public void move(Monster entity, Vector2 position) { 17 | entity.position(position); 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/wrap_class/Rendering.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.wrap_class; 2 | 3 | public class Rendering { 4 | public void render(Monster monster) { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/wrap_method/Main.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.wrap_method; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | Monster monster = new Monster(10, "Angry", new Vector2(0, 0)); 8 | monster.processAi(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/thoughtworks/legacycode/wrap_method/Monster.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.wrap_method; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | 5 | public class Monster { 6 | private Integer hitPoints; 7 | private String state; 8 | private Vector2 position; 9 | private Integer damage = 2; 10 | 11 | 12 | public Monster(Integer hitPoints, String state, Vector2 position) { 13 | this.hitPoints = hitPoints; 14 | this.state = state; 15 | this.position = position; 16 | } 17 | 18 | // Wrap this in a new method that call the method you want to test. Don't test this new method 19 | public void processAi(){ 20 | if (hitPoints > 5){ 21 | Monster nearestEnemy = findNearestEnemy(); 22 | if (state.equals("Angry")){ 23 | moveTo(nearestEnemy); 24 | attack(nearestEnemy); 25 | } else if (state.equals("Afraid")){ 26 | moveAwayFrom(nearestEnemy); 27 | } 28 | } else { 29 | rest(); 30 | } 31 | } 32 | 33 | public Monster findNearestEnemy() { 34 | return null; 35 | } 36 | 37 | public void rest() { 38 | hitPoints++; 39 | if (hitPoints > 5){ 40 | state = "Angry"; 41 | } 42 | } 43 | 44 | public void moveAwayFrom(Monster target) { 45 | Vector2 offsetFromMonsterToMe = position.minus(target.position); 46 | moveTo(position.add(offsetFromMonsterToMe)); 47 | } 48 | 49 | public void attack(Monster target) { 50 | target.hitPoints -= damage; 51 | } 52 | 53 | public void moveTo(Monster target) { 54 | moveTo(target.position); 55 | } 56 | 57 | public void moveTo(Vector2 position){ 58 | this.position = position; 59 | } 60 | 61 | public String state() { 62 | return state; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/com/thoughtworks/legacycode/adapt_parameter/ReportGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.adapt_parameter; 2 | 3 | import org.junit.Test; 4 | 5 | public class ReportGeneratorTest { 6 | 7 | // Adapt Parameter 8 | 9 | // Use adapter pattern on tough dependency 10 | // 11 | // Wrap the console parameter in a new class that we can then mock 12 | 13 | @Test 14 | public void shouldPrintConsoleInputSomething() { 15 | ReportGenerator reportGenerator = new ReportGenerator(); 16 | 17 | // Change 18 | reportGenerator.reportToConsole(System.console()); 19 | 20 | // Verify that we printed the string that the user entered into the console 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/com/thoughtworks/legacycode/break_out_method_object/AiEntityTest.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.break_out_method_object; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.mockito.Mockito.mock; 6 | import static org.mockito.Mockito.verify; 7 | import static org.mockito.Mockito.when; 8 | 9 | // Break out Method Object 10 | 11 | // Extract method you want to change into a new class and test that 12 | // There is a hint in the Monster class 13 | 14 | public class AiEntityTest { 15 | @Test 16 | public void shouldBecomeDeadWhenAtZeroOrFewerHitPoints() { 17 | Monster monster = mock(Monster.class); 18 | when(monster.hitPoints()).thenReturn(0); 19 | 20 | // Call method in new method object here 21 | 22 | verify(monster).state(""); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/thoughtworks/legacycode/encapsulate_global_reference/LibraryTest.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.encapsulate_global_reference; 2 | 3 | import org.junit.Test; 4 | 5 | public class LibraryTest { 6 | @Test 7 | public void shouldDoSomething() { 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/thoughtworks/legacycode/expose_static_method/LibraryTest.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.expose_static_method; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import static org.hamcrest.CoreMatchers.hasItem; 9 | import static org.junit.Assert.assertThat; 10 | 11 | public class LibraryTest { 12 | // Expose Static Method 13 | 14 | // Change existing method to be static (if it can be). You can test without an instance 15 | 16 | @Test 17 | public void shouldFindOverdueBooks() { 18 | List books = new ArrayList(); 19 | Book book = new Book("Starship Troopers", true); 20 | books.add(book); 21 | 22 | // Test will pause here while waiting for user input 23 | Library library = new Library(System.console()); 24 | 25 | // If overdueBooks was static we wouldn't have to create a new library 26 | List overdueBooks = library.overdueBooks(books); 27 | 28 | assertThat(overdueBooks, hasItem(book)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/com/thoughtworks/legacycode/extract_and_override_call/LibraryTest.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.extract_and_override_call; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.PrintStream; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import static org.mockito.Mockito.mock; 10 | import static org.mockito.Mockito.verify; 11 | 12 | public class LibraryTest { 13 | 14 | // Extract and Override Call 15 | 16 | // Extract tough dependency and override it then test child class 17 | 18 | @Test 19 | public void shouldPrintNameOfBookWhenThereIsOneBook() { 20 | List books = new ArrayList(); 21 | books.add(new Book("Ringworld")); 22 | PrintStream printStream = mock(PrintStream.class); 23 | 24 | // Create instance of a new subclass of Library and call printBooks 25 | 26 | verify(printStream).println("Ringworld"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/com/thoughtworks/legacycode/parameterize_constructor/ZooManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.parameterize_constructor; 2 | 3 | import org.junit.Test; 4 | 5 | public class ZooManagerTest { 6 | // Parameterize Constructor 7 | // Inject a dependency instead of leaving it internal to a class 8 | // 9 | // Break the dependencies on Animals.wild() and internally initialized enclosures so you can set them up properly for 10 | // this test case and verify the correct results 11 | 12 | 13 | @Test 14 | public void shouldPlaceAnimalInTheSameCageAsAlreadyCagedAnimalOfTheSameType() { 15 | ZooManager zooManager = new ZooManager(); 16 | zooManager.moveWildAnimalsToEnclosures(); 17 | // assert or verify that the new animal ended up in the correct enclosure 18 | } 19 | 20 | @Test 21 | public void shouldPlaceAllWildAnimals() { 22 | ZooManager zooManager = new ZooManager(); 23 | zooManager.moveWildAnimalsToEnclosures(); 24 | // assert or verify that there are no wild animals left 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/com/thoughtworks/legacycode/parameterize_method/LibraryTest.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.parameterize_method; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | import java.io.PrintStream; 7 | import java.security.spec.PSSParameterSpec; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import static org.mockito.Mockito.mock; 12 | import static org.mockito.Mockito.verify; 13 | 14 | public class LibraryTest { 15 | // Parameterize Method 16 | // Inject a dependency instead of leaving it internal to a method 17 | // 18 | // Break the dependency on BufferedReader by injecting it into the method 19 | 20 | @Test 21 | public void shouldPrintTheBookNameWhenThereIsOneBook() throws IOException { 22 | PrintStream printStream = mock(PrintStream.class); 23 | Library library = new Library(printStream); 24 | List books = new ArrayList(); 25 | books.add(new Book("House of Leaves")); 26 | 27 | library.printBooks(books); 28 | 29 | verify(printStream).println("House of Leaves"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/thoughtworks/legacycode/primitivize_parameter/MoveToMeetingPointCommandTest.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.primitivize_parameter; 2 | 3 | import org.junit.Test; 4 | 5 | public class MoveToMeetingPointCommandTest { 6 | @Test 7 | public void shouldDoSomething() { 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/thoughtworks/legacycode/pull_up_feature/AiEntityTest.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.pull_up_feature; 2 | 3 | import org.junit.Test; 4 | 5 | public class AiEntityTest { 6 | 7 | // Pull up Feature 8 | // 9 | // Pull the parts of a class you want to test into a new abstract base class then test a child of that 10 | 11 | @Test 12 | public void shouldMoveToClosestEnemyWhenHealthyAndAngry() { 13 | // Integer hitPoint = 10; 14 | // String state = "Angry"; 15 | 16 | // Create instance of new child class here 17 | 18 | // use stubs in test class to make sure the right interaction happen 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/thoughtworks/legacycode/push_down_dependency/AiEntityTest.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.push_down_dependency; 2 | 3 | import org.junit.Test; 4 | 5 | public class AiEntityTest { 6 | // Push down Dependencies 7 | 8 | // Make current class abstract and push your dependencies into a child class. Test through a new test child class. 9 | 10 | @Test 11 | public void shouldMoveToClosestEnemyWhenHealthyAndAngry() { 12 | // Integer hitPoints = 10; 13 | // String state = "Angry"; 14 | 15 | // Create instance of child class with stubbed out dependencies 16 | 17 | // check that moveTo was called with the Monster returned from findNearestEnemy 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/com/thoughtworks/legacycode/spawn_class/AiEntityTest.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.spawn_class; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.core.Is.is; 7 | import static org.junit.Assert.assertThat; 8 | 9 | 10 | // Spawn Class 11 | // 12 | // Introduce a new class for the test below and TDD a method on that class. Don't test that the new method is called by processAi 13 | // There is a hint in the Monster class 14 | 15 | public class AiEntityTest { 16 | @Test 17 | public void shouldBecomeDeadWhenAtZeroOrFewerHitPoints() { 18 | int hitPoints = 0; 19 | Monster monster = new Monster(hitPoints, "Angry", new Vector2(0, 0)); 20 | // Call new method here 21 | 22 | assertThat(monster.state(), is("Dead")); 23 | } 24 | } -------------------------------------------------------------------------------- /src/test/java/com/thoughtworks/legacycode/spawn_method/AiEntityTest.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.spawn_method; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.core.Is.is; 7 | import static org.junit.Assert.assertThat; 8 | 9 | // Spawn Method 10 | // 11 | // Introduce a method for the test below and TDD that method. Don't test that the new method is called by processAi 12 | // There is a hint in the Monster class 13 | 14 | public class AiEntityTest { 15 | @Test 16 | public void shouldBecomeDeadWhenAtZeroOrFewerHitPoints() { 17 | int hitPoints = 0; 18 | Monster monster = new Monster(hitPoints, "Angry", new Vector2(0, 0)); 19 | // Call new method here 20 | 21 | assertThat(monster.state(), is("Dead")); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/com/thoughtworks/legacycode/subclass_and_override_method/AiEntityTest.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.subclass_and_override_method; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.junit.Assert.assertThat; 8 | 9 | public class AiEntityTest { 10 | 11 | // Subclass and Override Method 12 | 13 | // Test a subclass of your real class and override methods with dependencies 14 | 15 | @Test 16 | public void shouldMoveToClosestEnemyWhenHealthy() { 17 | 18 | TestMonster monster = new TestMonster(10, "Angry", new Vector2(0, 0)); 19 | monster.nearestEnemy = new TestMonster(10, "", new Vector2(1,1)); 20 | monster.processAi(); 21 | 22 | assertThat(monster.enemyWeMovedTo, is(monster.nearestEnemy)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/com/thoughtworks/legacycode/wrap_class/AiEntityTest.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.wrap_class; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.core.Is.is; 7 | import static org.junit.Assert.assertThat; 8 | 9 | public class AiEntityTest { 10 | // Wrap Class aka Decorator 11 | 12 | // Wrap your hard to test class with a Decorator and TDD the decorator 13 | // 14 | // Create a new class that implements the GameEntity interface and also 'has-a' GameEntity 15 | 16 | @Test 17 | public void shouldBecomeDeadWhenAtZeroOrFewerHitPoints() { 18 | int hitPoints = 0; 19 | // Don't create an actual Monster here. Inject a mock GameEntity into your new decorator class 20 | GameEntity gameEntity = new Monster(hitPoints, "Angry", new Vector2(0, 0)); 21 | 22 | gameEntity.processAi(); 23 | 24 | assertThat(gameEntity.state(), is("Dead")); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/com/thoughtworks/legacycode/wrap_method/AiEntityTest.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.legacycode.wrap_method; 2 | 3 | import com.thoughtworks.legacycode.primitivize_parameter.Vector2; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.core.Is.is; 7 | import static org.junit.Assert.assertThat; 8 | 9 | public class AiEntityTest { 10 | //Wrap Method 11 | // New method (wraps the existing method and the method that will contain our new behavior) * Don't test 12 | // |------- Existing Method (processAi) * Don't test 13 | // |------- Method we want to TDD (this is where we make the monster dead if it has zero or fewer hitPoints 14 | // Introduce a method that contains an existing method and a call to you new method 15 | 16 | @Test 17 | public void shouldBecomeDeadWhenFewerThan1HitPoints() { 18 | Integer hitPoints = 0; 19 | Monster monster = new Monster(hitPoints, "Angry", new Vector2(0, 0)); 20 | 21 | // Call new method instead of processAi 22 | monster.processAi(); 23 | 24 | assertThat(monster.state(), is("DEAD")); 25 | } 26 | 27 | } 28 | --------------------------------------------------------------------------------