Testing and Refactoring Legacy Code
-
0:02 - 0:07Hi, my name is sandro mancuso
-
0:07 - 0:10in this screen cast
-
0:10 - 0:16we're gonna take a piece of existing code without test
-
0:16 - 0:18we're gonna write tests for it first
-
0:18 - 0:21and when it is 100% covered
-
0:21 - 0:24then we're gonna refactor it to make it better
-
0:24 - 0:26so, the business requirements are
-
0:26 - 0:29imagine a social networking website for travellers
-
0:29 - 0:34so, you need to be logged in to see any type of content
-
0:34 - 0:36and as soon as you logged in
-
0:36 - 0:37if you want to see someone else's trips
-
0:37 - 0:40you need to be friends with this person
-
0:40 - 0:42it's kind of like your facebook stuff
-
0:42 - 0:46so, if you're friends with someone you can see this person's trip
-
0:46 - 0:48otherwise, you can't
-
0:49 - 0:51so, but there are a few rules
-
0:51 - 0:56we can not touch production code if it's not covered by tests
-
0:57 - 1:03so, as we know, sometimes we should write test for existing code
-
1:03 - 1:05we need to change the existing code
-
1:05 - 1:10so the only exception to this rule is that you can use automatic refactoring
-
1:10 - 1:15by our IDE, never type into the file
-
1:16 - 1:20so, one approach that we gonna take is
-
1:20 - 1:24imagine that this is how your code looks like
-
1:24 - 1:29instead of trying to go to write test from the deepest branch
-
1:30 - 1:34we're gonna choose the shortest branch of the code and write a test for that
-
1:35 - 1:40and then we look at the second shortest branch
-
1:40 - 1:41and then write a test for that
-
1:41 - 1:45cause that gives us the ability to test by test
-
1:46 - 1:48understand what our code does
-
1:48 - 1:54and also we build up our test suite so we can get to the deepest branch
-
1:54 - 1:58if we start to write a test according to the deepest branch
-
1:58 - 2:00or trying to test the deepest branch
-
2:00 - 2:06normally we need to create a massive setup to get there
-
2:07 - 2:09so, we need to understand entire code base
-
2:09 - 2:13so we prefer to do from the shortest branch to the deepest
-
2:13 - 2:15let's see some code
-
2:15 - 2:20so this is the trip service class
-
2:21 - 2:24this is a server side class
-
2:25 - 2:27and basically what it does is
-
2:27 - 2:29it has a get trips by user
-
2:29 - 2:31receive a user in there
-
2:31 - 2:36so it checks the user session
-
2:36 - 2:38gets the user that is logged in
-
2:39 - 2:45then if the user is not logged in it throws an exception
-
2:45 - 2:51if the user is logged in then
-
2:51 - 2:57check if the logged in user is friends with the user passed as parameter
-
2:57 - 3:00and if they're friends then I can retrieve
-
3:00 - 3:04the list of trips for the user passed through parameter
-
3:04 - 3:08so a bit confusing for now but what I'm gonna do
-
3:08 - 3:11let's start writing test for it
-
3:11 - 3:15may I just split the screen
-
3:15 - 3:18that's normally how I prefer to code
-
3:18 - 3:20I normally split my screen into two
-
3:20 - 3:22I have my production code in one side
-
3:22 - 3:23my test on the other side
-
3:23 - 3:27then I don't need to keep switching from one to another
-
3:27 - 3:31so that's my favorite way of coding
-
3:31 - 3:34so, the first thing that we do
-
3:34 - 3:36we just look for the shortest branch
-
3:36 - 3:37we can ignore entire thing
-
3:37 - 3:39I don't care what this does for now
-
3:39 - 3:42for me, when I'm working with legacy code
-
3:42 - 3:43the first thing that I do is
-
3:43 - 3:45if I want to see what this code does
-
3:45 - 3:46I look for the shortest branch
-
3:46 - 3:49if the logged user is different from null
-
3:49 - 3:51then it throws an exception
-
3:51 - 3:54so if it is not logged in throws an exception
-
3:54 - 3:57so let's write a test to see if that's true
-
3:57 - 4:13so, should throw an exception when user is not logged in
-
4:13 - 4:17let's put the exception here
-
4:17 - 4:23expect it, user not logged in exception
-
4:24 - 4:27so let's create the trip service
-
4:33 - 4:35import that
-
4:35 - 4:37now what we should do
-
4:37 - 4:42trip service dot
-
4:42 - 4:44get trips by user
-
4:44 - 4:47pass as null here for now
-
4:50 - 4:52I'm using Infinitest
-
4:52 - 4:56so this red bar here basically show
-
4:56 - 4:59everytime that I save my test or my production code
-
4:59 - 5:01it runs my test for me
-
5:01 - 5:03so I don't need to keep trying with my hands
-
5:03 - 5:05this should be in theory
-
5:06 - 5:07green
-
5:07 - 5:10but something is happening
-
5:10 - 5:20so if I run junit by hand
-
5:20 - 5:23basically what's happening is
-
5:23 - 5:28this should throw user not loggedn in exception
-
5:28 - 5:31it's expecting user not logged in exception
-
5:31 - 5:35but it's throwing a dependent a class during blah blah
-
5:35 - 5:38so basically what's happening is
-
5:41 - 5:47when this line here is executed
-
5:49 - 5:55so when I say user session, get instance, get logged user
-
5:56 - 5:58the user session
-
5:58 - 6:01I did that on purpose of course
-
6:01 - 6:03throws an exception
-
6:03 - 6:06basically because in a unit test
-
6:06 - 6:11we shouldn't be invoking other classes
-
6:11 - 6:15because this may have the dependency of http session
-
6:15 - 6:18it could go to the database to retrieve stuff
-
6:18 - 6:20so we don't want that
-
6:21 - 6:23so we need to break these dependencies
-
6:24 - 6:26the problem that we have is
-
6:26 - 6:28this is a singleton
-
6:28 - 6:33and I cannot mock it right now
-
6:33 - 6:38so I can't inject it, I can't type inside this trip service
-
6:39 - 6:42but I need to find a way to get rid of it
-
6:42 - 6:44I need to find a way to mock that
-
6:44 - 6:48so the only things that I can do, as I said before
-
6:48 - 6:51is I need to use automated refactoring
-
6:51 - 6:52but I cannot not type
-
6:52 - 6:54so here what I'm gonna do
-
6:54 - 6:56I'm gonna create a seam
-
6:56 - 7:00so seam is the division of where the two classes
-
7:00 - 7:03the seam is where two classes meet
-
7:03 - 7:05the trip service and the user session
-
7:05 - 7:06so I'm gonna create this seam
-
7:06 - 7:09so what I'm gonna do, I'm gonna create a method
-
7:10 - 7:15saying get logged in user
-
7:17 - 7:18and I will make that protected
-
7:19 - 7:27so I just isolated the bit that goes to the other class
-
7:28 - 7:29and that's my seam
-
7:30 - 7:34so now what I can do as an intermediary step
-
7:34 - 7:45I can create a private class, testable trip service
-
7:45 - 7:49that extends the trip service
-
7:52 - 7:54so what I do
-
7:54 - 8:04I override the get logged in user
-
8:04 - 8:07and I make it return null
-
8:11 - 8:18and in my test, I replace the trip service by testable trip service
-
8:24 - 8:29and so if I try to run that
-
8:29 - 8:32now I have it green as you can see here
-
8:32 - 8:35my infinitest somtimes goes crazy
-
8:36 - 8:39sometimes I can trust, sometimes I cannot trust
-
8:39 - 8:45in this case it was red but my tests are green now
-
8:45 - 8:49so in theory I should be happy with that
-
8:50 - 8:53just to confirm, so my test is green
-
8:54 - 8:59and if I run my code coverage
-
9:00 - 9:06so I make sure that I'm testing the shortest branch
-
9:06 - 9:09so that's another good tip
-
9:09 - 9:11cause every time that I work with legacy code
-
9:11 - 9:13I always use code coverage
-
9:13 - 9:15so lots people thing code coverage ...
-
9:15 - 9:17ah... it's test coverage
-
9:17 - 9:19I don't care about the percentage
-
9:19 - 9:23the important thing for me here is, when I'm working with legacies
-
9:23 - 9:28is my test covering the branch that I thought it would cover
-
9:28 - 9:31because this avoid the false positives and negatives
-
9:31 - 9:33so I know exactly what I'm doing now
-
9:33 - 9:35so that's cool
-
9:35 - 9:42so let's remove that the code ceverage
-
9:47 - 9:50however this test is far from being good
-
9:50 - 9:53because look at this
-
9:53 - 9:56should throw an exception when user is not logged in
-
9:57 - 10:02this test is all about the user not being logged in
-
10:03 - 10:05but where does it say it in here
-
10:05 - 10:08here we created the testable trip service
-
10:08 - 10:11we invoke a method, pass null, throws an exception
-
10:11 - 10:15but where's the user not logged in?
-
10:17 - 10:19it's here, it's hidden
-
10:19 - 10:21so we need to fix that
-
10:21 - 10:24we need to introduce the concept
-
10:24 - 10:27oh by the way, the reason that I have this weird notation in here
-
10:27 - 10:30it's kind of weird for java people
-
10:30 - 10:32but normally the Ruby they use this convention
-
10:32 - 10:34because I can just collapse the method
-
10:34 - 10:39and then I have the specification of my class
-
10:39 - 10:40and normally I read like
-
10:40 - 10:45trip service should do something
-
10:45 - 10:50so that's how I normally prefer to create my test
-
10:50 - 10:56so this is all about the logged in user that is not there anywhere
-
10:56 - 10:58so I'm gonna create it
-
10:59 - 11:06logged in user is null
-
11:08 - 11:10so now I'm gonna create this guy
-
11:10 - 11:16create field user logged in user
-
11:18 - 11:23and I'm gonna say logged in user
-
11:24 - 11:29so now it is little bit better because I introduced the concept that I was talking about
-
11:32 - 11:35I don't like the nulls
-
11:35 - 11:39cause null doesn't mean much in this case
-
11:39 - 11:51so let's say logged in user is not logged in
-
11:51 - 11:55or in fact in a web application
-
11:57 - 12:01the opposite of logged in user is normally a guest
-
12:01 - 12:06so here we take the opportunity start using the concepts that
-
12:06 - 12:11you probably gonna speak the behavior to business people
-
12:11 - 12:14right, so it started to use your domain language
-
12:15 - 12:22in here, I have the user that should be friends with or not
-
12:23 - 12:26in this case, I prefer to make it explicit that is
-
12:28 - 12:31unused user
-
12:40 - 12:45so now, see if I can
-
12:46 - 12:53I'm just trying to see if I can make my Infinitest be happy
-
12:54 - 12:55but it's not
-
12:55 - 12:58so I won't be trusting my Infinitest unfortunately
-
13:02 - 13:05so I much happier with my test now
-
13:05 - 13:08and by the way my Infinitest is working again
-
13:09 - 13:12so now my test reflex what I was talking about
-
13:12 - 13:15so should throw an exception when user is not logged in
-
13:15 - 13:17so the logged in user is a guest
-
13:17 - 13:19and then throws an exception
-
13:19 - 13:20so awesome
-
13:20 - 13:22so what we can do right now is
-
13:22 - 13:23rerun the code coverage
-
13:30 - 13:32I just want to run the junit first
-
13:32 - 13:34just to make it sure
-
13:34 - 13:35cool
-
13:35 - 13:37now I can run my code coverage
-
13:43 - 13:45so now what I do is
-
13:45 - 13:47according to my code coverage
-
13:47 - 13:51I can easily see what is the second shortest branch
-
13:52 - 13:56so basically, now is this for loop in here
-
13:57 - 14:00and then I say if it's friend
-
14:00 - 14:02so this is the deepest
-
14:02 - 14:06cause to get to here this flag needs to be true
-
14:06 - 14:10so basically what I need now is when the user is logged in
-
14:10 - 14:14but he's not friends with the user passed as parameter
-
14:15 - 14:20so we're gonna write the test for that
-
14:20 - 14:26so let's keep the code coverage on
-
14:28 - 14:32so let's say
-
14:34 - 14:48should not return any trips when users are not friends, right?
-
14:48 - 14:54so I'm gonna borrow these guys here for seconds
-
14:57 - 15:00so now I need a logged in user
-
15:00 - 15:04so I say, a registered user
-
15:05 - 15:07let's create this guy
-
15:14 - 15:24and here what I would need is a list trip trips
-
15:24 - 15:30let's call it friend trips
-
15:30 - 15:32so import all that
-
15:43 - 15:45so basically what I need now
-
15:45 - 15:46I need a proper user
-
15:46 - 15:49I can't have a user in here
-
15:49 - 15:51so I'll call it a
-
15:52 - 15:55let's call it friend
-
15:56 - 16:00so I need a user that is friend
-
16:04 - 16:11so add friend, another user
-
16:14 - 16:19friend, let's say add trip to brazil
-
16:22 - 16:25so let's create these guys
-
16:41 - 16:45ans so now I have a friend
-
16:45 - 16:49that is friend with another user that has a trip to brazil
-
16:49 - 16:53I have a logged in user that is a valid user
-
16:53 - 16:56but they're not friends
-
16:56 - 17:02so I need to assert that
-
17:04 - 17:09friend trip dot size is zero
-
17:11 - 17:14right, I could have said
-
17:14 - 17:16assert that is empty doesn't matter
-
17:16 - 17:20so my Infinitest that apparently is working
-
17:20 - 17:25just confirm running the JUnit command
-
17:25 - 17:27so it saying that it is green
-
17:27 - 17:30that happens quite often in legacy code
-
17:30 - 17:31cause the code is already there
-
17:31 - 17:33you just making sure that you understand what code does
-
17:33 - 17:35so that's quite common
-
17:35 - 17:37I'm just run the code coverage
-
17:38 - 17:41so running the code coverage now we progressed little bit
-
17:41 - 17:45so as you can see my new test now moved forward
-
17:45 - 17:46so went for the shortest branch
-
17:46 - 17:49where the user was logged in
-
17:49 - 17:51but they were not friends
-
17:52 - 17:57so it was returning always false and then that it
-
17:57 - 17:59so we're not done yet
-
17:59 - 18:02because now we need to do some refactoring here
-
18:02 - 18:04there is some duplication
-
18:04 - 18:06so let's get rid of it
-
18:07 - 18:11so let's put a before method
-
18:13 - 18:15let's transform this guy
-
18:15 - 18:19local variable into field
-
18:19 - 18:21it's great
-
18:21 - 18:22it's awsome
-
18:25 - 18:27now we can move this guy up
-
18:36 - 18:37right
-
18:38 - 18:44so now I can go to the deepest branch
-
18:44 - 18:46so now what I need is
-
18:46 - 18:52when the logged in user is friends with the user passed as parameter
-
18:53 - 18:56then I need the trips, return the trips
-
19:00 - 19:08should return friend trips when users are friends
-
19:09 - 19:11right? so that's the one we testing
-
19:11 - 19:14so I'm gonna borrow this again
-
19:23 - 19:25so you shouldn't do copy and pate
-
19:25 - 19:27I don't do copy and paste
-
19:27 - 19:29what you saw was an illusion
-
19:29 - 19:33so I never, never ever do copy and paste
-
19:34 - 19:38so in this case here what I want you to do now is
-
19:39 - 19:46friend, add friend with logged in user
-
19:47 - 19:49let's add another trip
-
20:03 - 20:08so now of course that I expect my Infinitest are red saying with this
-
20:08 - 20:12red, of course I expect this should be true
-
20:12 - 20:14because now the users are friends
-
20:14 - 20:15and if I save
-
20:15 - 20:19it still saying that it's red
-
20:19 - 20:22so let's run junit by hand
-
20:22 - 20:25oops, I keep getting the shortcuts wrong
-
20:25 - 20:28because I kept using windows
-
20:28 - 20:33my mac and intellij keep getting (???)
-
20:33 - 20:37so what it's saying is, because now what's happening
-
20:38 - 20:39just restore this screen
-
20:40 - 20:42what's happening is
-
20:42 - 20:47the code is now getting here in the trip dao
-
20:47 - 20:49that is a statical
-
20:50 - 20:53and as soon as the code execute this guy
-
20:53 - 20:55then there's an exception being thrown
-
20:55 - 20:57now I give the same thing
-
20:57 - 21:00so this is just to prove the point that
-
21:00 - 21:01just to make a point
-
21:01 - 21:07that we shouldn't ever be calling or execute another class in a unit test
-
21:07 - 21:13because this, in the real world, would be going to the database and fetching data blah blah blah
-
21:13 - 21:17the test for my trip service shouldn't care about that
-
21:17 - 21:22so we need to do exactly the same thing
-
21:22 - 21:25that we've done for user session
-
21:25 - 21:30so you can break the hard wired dependencies
-
21:30 - 21:33like singleton and staticals
-
21:33 - 21:37or objects being created inside method is exactly the same
-
21:37 - 21:39so basically what we do
-
21:40 - 21:42create a method
-
21:42 - 21:49let's say trips by user
-
21:49 - 21:51that is also protected
-
21:51 - 21:56and then we go back to our tests
-
22:02 - 22:04we'll do the same thing
-
22:17 - 22:20so basically what we'll do
-
22:20 - 22:22we will just return
-
22:22 - 22:24so we will receive the user
-
22:24 - 22:27the user in our test that has two trips
-
22:29 - 22:32so basically we just return that
-
22:32 - 22:35the user dot trips
-
22:36 - 22:39so as soon as do that my test is green
-
22:40 - 22:44so if I run my code coverage
-
22:44 - 22:45awesome
-
22:45 - 22:51cause now I have all my existing code base
-
22:51 - 22:54tested to almost 100%
-
22:54 - 23:00of course that you might say that I created are not test because I overriding them
-
23:00 - 23:02but that's alright because that's just my seam
-
23:02 - 23:05that's a very thin layer to delegate
-
23:05 - 23:07and that's intermediatary stage anyway
-
23:07 - 23:12so now few clean ups that we can do
-
23:12 - 23:13normally what I do is
-
23:13 - 23:19because this guy is here and is here
-
23:19 - 23:25and the only situation that I don't want it should be like that is this one
-
23:25 - 23:26so I leave this one
-
23:26 - 23:28and I just move this one out
-
23:30 - 23:32because then it is common to all other tests
-
23:32 - 23:37all other tests that I want the logged in user should be a valid one
-
23:37 - 23:40so once I do that
-
23:40 - 23:42if I save
-
23:42 - 23:44then if I just make it sure
-
23:44 - 23:46so all my tests are green
-
23:46 - 23:48and that's great
-
23:48 - 23:52so for the first part, it's great we're done
-
23:52 - 23:56now our code is tested and we're free to start refactoring
-
23:57 - 24:02so now that we're in a very good point to commit
-
24:02 - 24:06so git status
-
24:06 - 24:08git commit
-
24:08 - 24:18unit tests for trip service
-
24:18 - 24:20cool, awesome
-
24:24 - 24:26look at this test, the problem is
-
24:26 - 24:30legacy code is that quite often you need
-
24:30 - 24:32this is of course very simple one
-
24:33 - 24:39but imagine like a same method with hundreds of line
-
24:39 - 24:42and here we're just talking about one object being created
-
24:42 - 24:43to pass in as parameter
-
24:43 - 24:48but quite often what we do is we need to create this massive object graph
-
24:48 - 24:50to pass in as parameter
-
24:50 - 24:53so we can reach all the branches and stuff
-
24:53 - 24:55so we can start cleaning that up
-
24:55 - 24:58cause I don't like all these duplication in here and here
-
24:58 - 25:02so the way that I would love to see it
-
25:02 - 25:04is using a builder
-
25:04 - 25:09so basically what I loved to see is something like that
-
25:09 - 25:12user friend
-
25:12 - 25:19kind of like a friend or a user
-
25:24 - 25:35friends with another user and logged in user
-
25:36 - 25:46with trips to brazil and to london
-
25:47 - 25:50so that's the sort of stuff that I would like to see
-
25:50 - 25:52I think I read far better
-
25:52 - 25:55so let's try to see if we can make that happen
-
25:55 - 25:58so let's put a build method in here
-
25:58 - 26:00let's use the builder pattern for that
-
26:02 - 26:05let's say user builder
-
26:07 - 26:12so that would be what I want to see
-
26:12 - 26:16so I will gonna comment this out a little bit
-
26:17 - 26:25so I'll say, public, static
-
26:25 - 26:28class, user builder
-
26:31 - 26:34let's start creating this method in there
-
26:48 - 26:50the way that you can chain methods
-
26:50 - 26:53you'll just keep returning the same object
-
26:53 - 26:55and then you can chain all the methods
-
26:55 - 26:57so let's create the use builder
-
27:01 - 27:05so now we just create the next method
-
27:08 - 27:10keep returning the user builder
-
27:10 - 27:15so here we're passing two users
-
27:17 - 27:22as I always said, there are just three numbers in programming
-
27:22 - 27:24there is 0, there is 1 and there is many
-
27:24 - 27:27there's nothing more than that
-
27:27 - 27:30so let's make it many
-
27:31 - 27:33so let's use variable arguments
-
27:33 - 27:37cause that I can pass many users as I want
-
27:38 - 27:41so let's put friends
-
27:43 - 27:48let's say these friends equals friends
-
27:48 - 27:51let's keep returning same objects
-
27:51 - 27:53let's create this guy
-
27:53 - 27:58should never leave arrays uninitialized
-
27:58 - 28:01so new user
-
28:05 - 28:08avoid some null pointer exception
-
28:09 - 28:16so great, let's go for our second method
-
28:16 - 28:19so user builder
-
28:19 - 28:22let's do the same thing
-
28:22 - 28:28because then we're free to use our method to pass as many trips as we want
-
28:37 - 28:39let's return trips
-
28:39 - 28:41let's create trips
-
28:41 - 28:45normally what I do
-
28:45 - 28:49I write the way I want to read it
-
28:49 - 28:52and then I start creating all the code from there
-
28:53 - 28:57and let's create the build method
-
28:57 - 29:04so build method, I like it to be the last method
-
29:04 - 29:11so the build method is the method that actually build the object
-
29:12 - 29:15so new user
-
29:16 - 29:17let's return the user
-
29:17 - 29:19and then I say
-
29:20 - 29:23add trips to user
-
29:23 - 29:29add friends to user
-
29:32 - 29:38it's always to cool to combine the method name and the parameter
-
29:38 - 29:41it makes it much easier to read it
-
29:41 - 29:43avoid duplication in names as well
-
29:43 - 29:46so let's do the trips first
-
29:46 - 29:50so for each
-
29:50 - 29:52here we're doing the trips
-
29:52 - 29:56so that's a trip that's a trip that's trips
-
29:56 - 30:03so user dot add trip, trip
-
30:04 - 30:07let's do the same thing for friends
-
30:07 - 30:09create add friends
-
30:11 - 30:12let's do
-
30:12 - 30:15for each
-
30:15 - 30:17so that's user
-
30:17 - 30:20that's friend
-
30:20 - 30:22friends
-
30:22 - 30:28user dot add friend friend
-
30:29 - 30:31awesome, cool
-
30:31 - 30:36so now, Infinitest saying everything is green
-
30:36 - 30:40so I can delete this
-
30:41 - 30:44and I can replace this one as well
-
30:44 - 30:45so let's replace this one
-
31:15 - 31:19awsome, let's comment that out
-
31:19 - 31:22so if I run everything, still green
-
31:22 - 31:24again, awesome
-
31:25 - 31:30so now my tests are slightly more readable
-
31:30 - 31:34so what I will do move this builder out
-
31:34 - 31:35so let's say
-
31:35 - 31:37move type to new file
-
31:39 - 31:41and basically what if you do
-
31:41 - 31:48if you just move this class the same folder where the TripServiceTest is
-
31:48 - 31:49for now it's good
-
31:50 - 31:52so now that is out
-
31:52 - 31:56static import this methods in here
-
31:56 - 31:59static import this methods in here
-
31:59 - 32:02so let's make code symmetric
-
32:02 - 32:05so both of them are symmetric
-
32:05 - 32:09and let's remove all the unutilized import
-
32:09 - 32:12so there's no unused imports anywhere
-
32:12 - 32:14so excellent
-
32:14 - 32:17so now I'm much much happier with my test
-
32:17 - 32:20because then I can read it properly
-
32:20 - 32:23and that's a great point to commit as well
-
32:23 - 32:24so let's see
-
32:24 - 32:26we got status
-
32:26 - 32:29git add
-
32:30 - 32:31git commit
-
32:31 - 32:46user builder created and used in trip service test
-
32:46 - 32:49cool, awesome
-
32:53 - 32:56so now we're finally ready to start refactoring this
-
32:56 - 32:58as we're saying before
-
32:58 - 33:00where was it, here
-
33:02 - 33:07we normally start testing from the shortest branch to the deepest
-
33:08 - 33:10and when we're doing refactoring it's the other way around
-
33:10 - 33:13you start from the deepest branch
-
33:14 - 33:17to the shortest delta branches
-
33:18 - 33:19the reason beneath is
-
33:19 - 33:22if you want to start refactoring in the middle of your code
-
33:22 - 33:24you need to understand everything
-
33:24 - 33:25and normally
-
33:27 - 33:30legacy code, they have global variable everywhere
-
33:30 - 33:31and dependencies
-
33:31 - 33:34set a flag in one place, checks it on the other
-
33:34 - 33:37but if you go straight to the deepest branch
-
33:37 - 33:40the deepest branch doesn't depend on anything
-
33:40 - 33:43it normally receives everything that it needs that is needs
-
33:43 - 33:45and then it does something with it
-
33:45 - 33:48if you're able to extract that
-
33:48 - 33:53refactor that the deepest branch from deepest to shortest
-
33:53 - 33:54you're code is start shrinking
-
33:54 - 33:57so let's look at the code
-
33:57 - 34:00so the deepest branch of the code
-
34:00 - 34:02as we discussed before is this one
-
34:02 - 34:05so to get to here
-
34:05 - 34:07I need this flag to be set
-
34:07 - 34:09the flag is set here
-
34:09 - 34:12the problem in this example in specific
-
34:12 - 34:14the deepest branch that is this line
-
34:14 - 34:15doesn't do much
-
34:15 - 34:18it just delegates to this one
-
34:18 - 34:21so I can re-do anything with one line
-
34:21 - 34:25so the second deepest branch is this one
-
34:26 - 34:28right it is this bit in here
-
34:28 - 34:30so let's go for this one
-
34:32 - 34:34one thing that is very very important
-
34:34 - 34:36when working with legacy code
-
34:36 - 34:41is to understand why the method is so big
-
34:41 - 34:44why is it so complicated?
-
34:44 - 34:51and normally it is because the method, of course, does too much
-
34:51 - 34:53that's why it has so many lines
-
34:53 - 34:58so it's important that instead of us trying to make the algorithm better
-
34:58 - 35:01or extract method in here
-
35:01 - 35:03we should ask ourselves
-
35:03 - 35:05does this responsibility
-
35:05 - 35:08does this behavior belong here?
-
35:08 - 35:11in this case, look at this
-
35:13 - 35:14if you look at this block
-
35:14 - 35:24so we go to the user object and asks for the friends
-
35:24 - 35:28so we get a list of friends out of the user object
-
35:28 - 35:30we iterate through it
-
35:30 - 35:34just to ask if the logged user is inside that collection or not
-
35:34 - 35:36so this is called feature envy
-
35:36 - 35:40it's kind of the trip serivce envies the user
-
35:40 - 35:42it wants to be the user class
-
35:42 - 35:48so what we could do here, we need to solve the feature envy
-
35:48 - 35:53if the user class has the collection of friends
-
35:53 - 35:55we should ask the user class, like
-
35:55 - 35:59user are you friends with this guy?
-
35:59 - 36:03so, let's try to move this behavior there
-
36:03 - 36:05so the first thing is
-
36:05 - 36:07let's look at the user class
-
36:07 - 36:09where's my user?
-
36:10 - 36:13so let's close this too
-
36:13 - 36:15so that's my user class
-
36:17 - 36:19let's create a test for it
-
36:19 - 36:24so my user class is here
-
36:24 - 36:30new class, let's say user test
-
36:30 - 36:35but I'm gonna put that in the test package of course
-
36:36 - 36:40so that will create my user test in here
-
36:40 - 36:44let's move my test here
-
36:44 - 36:47cool, so what do I want?
-
36:47 - 36:53I want this behavior here should be in this class
-
36:53 - 36:55I want to go to the user and say,
-
36:55 - 36:58"Are you friends with another user?"
-
36:58 - 36:59right?
-
36:59 - 37:02so, I'm gonna write this test
-
37:06 - 37:11should inform when users are not friends
-
37:11 - 37:13I'm gonna start with not friends
-
37:13 - 37:15because it is the easiest test that I can write
-
37:15 - 37:17this is the simplest one
-
37:19 - 37:22here I can use my builder
-
37:22 - 37:35user builder dot a user
-
37:35 - 37:45so let's say that this guy is friends with Bob
-
37:45 - 37:49right? let's do that
-
37:50 - 37:54and then in my test, I'm gonna say
-
37:55 - 38:13assert that user is friends with Paul is false
-
38:15 - 38:21so basically I want to create a user that is friend with Bob
-
38:21 - 38:25and of course if I ask this user if he's friend with Paul
-
38:25 - 38:26it should be false
-
38:26 - 38:30so let's create Bob and Paul
-
38:38 - 38:40let's create Paul as well
-
38:49 - 38:53so now that's the method that I want the user class have
-
38:53 - 38:55let's create it
-
39:00 - 39:06and of course the minimum behavior that I can have to satisfy the test
-
39:06 - 39:08is false
-
39:08 - 39:10so as soon as I save the false
-
39:10 - 39:15my Infinitest tells me that it's passing
-
39:15 - 39:16that's awesome, great
-
39:17 - 39:23so let's do the other case, when the users are friends
-
39:23 - 39:35it should inform when users are friends
-
39:38 - 39:42so I'm gonna borrow these guys here
-
39:42 - 39:44let's move that down
-
39:44 - 39:47and what I'm gonna do is
-
39:47 - 39:51you're also friend with Paul
-
39:51 - 39:58and I expect it, of course
-
39:58 - 40:00it should be true
-
40:00 - 40:05so my Infinitest are saying that it's not true
-
40:06 - 40:08now I'm gonna just implement
-
40:08 - 40:11just friends dot contain
-
40:13 - 40:14Paul doesn't work here
-
40:14 - 40:18let's say another user
-
40:26 - 40:32cool, so now I have my method in there
-
40:32 - 40:36awsome so of course that is covered
-
40:36 - 40:38there are ones that are not used in this one
-
40:38 - 40:40that's alright
-
40:41 - 40:44so this is a great point to commit
-
40:46 - 40:48git status
-
40:48 - 40:49git add
-
40:49 - 40:51so git commit
-
40:55 - 41:03is friends with method added to user
-
41:04 - 41:06awesome
-
41:08 - 41:12so now we can go back to our test
-
41:12 - 41:19just remove all the recorded code coverage marks
-
41:21 - 41:24now I have the user with the behavior that I want
-
41:25 - 41:26so when we start to refactoring
-
41:26 - 41:29we should try to stay in the green
-
41:29 - 41:31for as long as possible
-
41:31 - 41:33instead of going crazy in the refactoring
-
41:34 - 41:36and say I'm gonna change everything
-
41:36 - 41:37and then all the tests are broken
-
41:37 - 41:40we should, every single step that we make
-
41:40 - 41:42we should run our tests
-
41:42 - 41:44using Infinitest, is a way works well
-
41:44 - 41:47is fantastic because that
-
41:47 - 41:50if you make one move save it and it runs your test
-
41:50 - 41:51and then you know where you are
-
41:51 - 41:55and as soon as you make one move
-
41:55 - 41:57something is broken
-
41:57 - 42:00so your test shows the red in there
-
42:00 - 42:04you're one Ctrl+Z away to be back to green
-
42:04 - 42:06you just Ctrl+Z and save it
-
42:06 - 42:07you're back into green
-
42:07 - 42:12so let's try to make the smallest step that we can
-
42:12 - 42:16to do this refactoring and stay in the green, right?
-
42:19 - 42:22so what if I say
-
42:22 - 42:29what if I first of all move this boolean in here
-
42:29 - 42:34because it just used inside of the if statement so I can bring
-
42:34 - 42:35oh, there's another tip
-
42:35 - 42:36when working with legacy code
-
42:36 - 42:40normally you find variables declared all over the place
-
42:40 - 42:42try to bring them together
-
42:42 - 42:48try to put the blocks of code close each other
-
42:48 - 42:52if the variable is not used at the top
-
42:52 - 42:53it's just used at bottom
-
42:53 - 42:54but it's declared at the top
-
42:54 - 42:56bring that close
-
42:56 - 42:58because it ??? know where the blocks are
-
42:58 - 43:01and it makes you much easier to separate the code
-
43:01 - 43:03so now I could say
-
43:03 - 43:10is friend, I could say
-
43:10 - 43:17user is friends with logged user
-
43:17 - 43:22let's rename logged user to logged in user
-
43:22 - 43:24so that's how I think
-
43:24 - 43:25when you're dealing with legacy
-
43:25 - 43:29you find names are quite weird or not quite right
-
43:29 - 43:30as soon as you figured out where they are
-
43:30 - 43:32just rename it quickly
-
43:32 - 43:33so cool, as you can see
-
43:33 - 43:38you can keep an eye on the green bar here
-
43:38 - 43:39so every time that I saved it
-
43:39 - 43:41it's gonna run my test
-
43:41 - 43:47so now, I believe that is quite safe
-
43:47 - 43:49to do this and save
-
43:49 - 43:53so it ran my tests and that's green
-
43:53 - 43:54so now I can delete that
-
43:55 - 43:57so now that I can delete
-
43:58 - 44:00I maybe able to
-
44:10 - 44:13let's try to inline this
-
44:13 - 44:14awesome
-
44:15 - 44:16so that's cool
-
44:18 - 44:23so there is a quick win as well here
-
44:23 - 44:26for example, this guy here
-
44:26 - 44:28it's a guard clause
-
44:28 - 44:30we can transform this into guard clause
-
44:30 - 44:31what is a guard clause?
-
44:31 - 44:33as soon as parameter comes in
-
44:33 - 44:36you invalidate parameter and throw an exception
-
44:36 - 44:37that's the guard clause
-
44:37 - 44:40so what we can do
-
44:40 - 44:45let's transform that to guard clause
-
44:45 - 44:46we can get rid of if
-
44:48 - 44:53so if logged in user is null
-
44:56 - 44:58then
-
45:00 - 45:03let's throw an exception
-
45:03 - 45:07of course that this guy need to be up
-
45:09 - 45:12so as soon as I saved it
-
45:12 - 45:14so I'm still in the green
-
45:14 - 45:20and Eclipse kindly showing to me that this is dead code now
-
45:20 - 45:24so I can delete the entire if statement
-
45:28 - 45:29so if I save it
-
45:30 - 45:31awesome
-
45:31 - 45:35so I'm doing all these refactoring without going to the red
-
45:35 - 45:37not even a single time
-
45:37 - 45:40but even if I make a mistake and I go to the red
-
45:40 - 45:43what normally happens in the real world
-
45:43 - 45:46that's alright, I do a Ctrl+Z
-
45:46 - 45:47I'm back into green
-
45:47 - 45:48and I re-think what I'm doing
-
45:48 - 45:51sometimes you need to go to the red
-
45:51 - 45:54but I avoid it as much as I can
-
45:54 - 45:57so now we have two clear blocks in here
-
45:59 - 46:06the first block is about checking or validating the logged in user
-
46:06 - 46:14the second block is about getting the trips from the friend
-
46:14 - 46:17so we can do a few things slightly better in here
-
46:17 - 46:20so for example I could say
-
46:23 - 46:26getting rid of variables in legacy code
-
46:26 - 46:29is another thing that you should try to do
-
46:29 - 46:32because the less variables you have the easier it is
-
46:32 - 46:37even if you have some redundancy in there
-
46:37 - 46:39if you call method twice
-
46:39 - 46:41you may sacrifice performance for a while
-
46:42 - 46:44but getting rid of variable
-
46:44 - 46:47cause variables are the most people ??? in long method
-
46:47 - 46:50because they're set in many different places and reused
-
46:50 - 46:52if you can get rid of them
-
46:52 - 46:56and just invoke the same method over and over again
-
46:56 - 46:58it'll be easier to read all the code
-
46:58 - 47:02and then you do the optimization for ??? again, if you need to
-
47:02 - 47:10so for example, what I'm trying to do here is
-
47:16 - 47:18so saved it
-
47:18 - 47:19I'm still in the red
-
47:20 - 47:26so I believe that what I could even do is say
-
47:26 - 47:27return
-
47:30 - 47:32so I'm still in the green
-
47:33 - 47:35so return
-
47:36 - 47:37of course it don't need
-
47:37 - 47:40so if I save that I'm still in the green
-
47:40 - 47:42and I don't need the variable
-
47:42 - 47:44don't need that
-
47:44 - 47:46still in the green
-
47:46 - 47:49and now that I have symmetrical
-
47:49 - 47:53if I can use ternary operator
-
47:53 - 47:58so I could do that for exmple
-
48:12 - 48:14hopefully if I save that
-
48:14 - 48:15I'm still in the green
-
48:15 - 48:16awesome
-
48:16 - 48:18another thing is
-
48:18 - 48:21this variable here is used in two different places
-
48:21 - 48:22so let's inline that
-
48:22 - 48:24inline from here. yes, I can
-
48:24 - 48:26it's inlined in both places
-
48:26 - 48:27that's what I was talking about
-
48:27 - 48:29I got rid of the variable
-
48:29 - 48:33and of course I'm calling the method twice
-
48:33 - 48:35but I made my code a lot simpler
-
48:35 - 48:36of course if there's performance issue
-
48:37 - 48:40then you extract variables again all sort of stuff
-
48:40 - 48:45so that's awesome because now I'm much happier
-
48:45 - 48:49and one thing that I don't even like this guy here
-
48:51 - 48:55let's extract method in here, and say
-
48:55 - 48:58no trips, let's make it private
-
49:02 - 49:04so the cool thing about it is
-
49:06 - 49:08that for example
-
49:10 - 49:12if we look at the code
-
49:12 - 49:14if I look at my test now
-
49:15 - 49:19should throw an exception when user is not logged in
-
49:19 - 49:21get logged in user
-
49:21 - 49:22throw an exception
-
49:22 - 49:27should not return any trips when users are not friends
-
49:27 - 49:30if user is friends with logged in user
-
49:30 - 49:31no trips
-
49:31 - 49:34should return friend's trips when users are friends
-
49:34 - 49:36so if user is friend with logged in user
-
49:36 - 49:38trips by user
-
49:38 - 49:45so you end up saying the same language used in the test or test names
-
49:45 - 49:46being reflected in your code
-
49:46 - 49:48how your code is written
-
49:48 - 49:50that's one of the things that I want you to achieve
-
49:50 - 49:55so that's another good place to stop and commit
-
49:55 - 49:57so let's do that
-
49:57 - 49:59so git status
-
49:59 - 50:02git commit
-
50:03 - 50:08trip service refactored
-
50:09 - 50:10cool
-
50:12 - 50:14so that's awesome
-
50:15 - 50:20so we may think that this is good, right?
-
50:20 - 50:22we are done
-
50:22 - 50:23but NO.
-
50:23 - 50:26we are far from being done
-
50:28 - 50:30see, there's tiny piece of code
-
50:31 - 50:35but for example, we have enormous problems
-
50:36 - 50:39one of the problem is
-
50:39 - 50:41what we've done here is
-
50:41 - 50:46we took a piece of untested legacy code
-
50:46 - 50:48and then we wrote test straight
-
50:48 - 50:50and when we do that
-
50:50 - 50:52that's great, that's awesome
-
50:52 - 50:56cause then we feel confident that we can change it
-
50:56 - 50:59(???)
-
50:59 - 51:02but the drawback is that
-
51:02 - 51:04what if the design is wrong?
-
51:06 - 51:08cause if the design is wrong
-
51:08 - 51:10what we're doing when we write test
-
51:10 - 51:13we're perpetuating that design
-
51:13 - 51:15so we need to be very careful
-
51:15 - 51:19because if we're not very inclined to change the design
-
51:19 - 51:22and now we have a battery of test covering that stuff
-
51:22 - 51:24we're far less inclined now(???)
-
51:24 - 51:25because as soon as we start to change design
-
51:25 - 51:27you'll break all those tests
-
51:27 - 51:29so you need to be careful with that
-
51:29 - 51:32and why the design here is wrong?
-
51:32 - 51:35well, by the name
-
51:36 - 51:41I assume the trip service is a server side class
-
51:41 - 51:43belongs to the Model
-
51:44 - 51:51but then it has a dependency on the user session, right?
-
51:51 - 51:57so your Model shouldn't know anything about the web framework
-
51:57 - 52:01shouldn't know anything about http session or anything like that
-
52:02 - 52:05so, that's the problem with the design that this class has
-
52:05 - 52:10so we need to solve that
-
52:10 - 52:12one way to solve that is
-
52:12 - 52:17we should pass the logged in user as parameter to this method
-
52:17 - 52:23so now I'm gonna make sort of dangerous refactoring
-
52:23 - 52:26that it will be ok for this exercise
-
52:26 - 52:29but if you do that in a normal production environment
-
52:29 - 52:30you need to be very careful
-
52:30 - 52:31so I want to pass
-
52:31 - 52:33I'll try to do, as always
-
52:33 - 52:35baby steps, bit by bit
-
52:35 - 52:40so for example I want to pass the logged in user as parameter
-
52:40 - 52:43so what I can do is
-
52:43 - 52:46I go to refactor
-
52:46 - 52:48change method signature
-
52:48 - 52:51and I'm gonna add a new parameter
-
52:51 - 52:53I'm gonna call user
-
52:55 - 52:59logged in user, right?
-
52:59 - 53:01and the default is null
-
53:02 - 53:04so that's cool
-
53:04 - 53:08because eclipse does great job in refactoring
-
53:08 - 53:10IntelliJ does same
-
53:10 - 53:13so this guy was passed in as parameter
-
53:13 - 53:15it's not used anywhere else
-
53:15 - 53:18but it changed in my test
-
53:18 - 53:23it changed the method signature to pass null, everywhere, right?
-
53:23 - 53:26so that's cool, that's great
-
53:26 - 53:29the only problem is, for example
-
53:29 - 53:32this method here being a production method
-
53:32 - 53:34it's probably used
-
53:34 - 53:37or it's probably invoked by other classes
-
53:37 - 53:45and now all the other places where this method here is invoked
-
53:45 - 53:47the production code is passing null as well
-
53:47 - 53:50so you need to go back there and fix that
-
53:50 - 53:52so you just need to bear that in mind
-
53:53 - 53:56but for the sake of this exercise
-
53:58 - 54:00so what we want to do
-
54:00 - 54:01we want to do fix our test
-
54:01 - 54:04cause we don't want this nulls hanging in around
-
54:04 - 54:05and this is the logged user
-
54:05 - 54:08so I'm gonna pass here, a guest
-
54:08 - 54:10because that's what I would pass
-
54:12 - 54:15so as always I'm looking at my Infinitest
-
54:15 - 54:22so here I'm gonna pass my logged in user
-
54:22 - 54:24here I'm gonna pass
-
54:27 - 54:30yeah, I'm gonna pass my logged in user here
-
54:36 - 54:37so that's cool
-
54:37 - 54:39just to make sure that everything is ok
-
54:39 - 54:41so cool
-
54:42 - 54:45now I want to start using this parameter
-
54:47 - 54:48so what I'll do
-
54:49 - 54:54I'll just use the parameter
-
54:54 - 54:59I'm just gonna rename that to this
-
54:59 - 55:04rename that to this
-
55:04 - 55:07so this is the parameter now
-
55:07 - 55:09so if i save it
-
55:11 - 55:14and so all my tests are green
-
55:14 - 55:16so that means that this guy
-
55:16 - 55:19transform that in private
-
55:19 - 55:22so eclipse say that it's not been used
-
55:22 - 55:25so I can delete safely
-
55:26 - 55:30and of course, as soon as I delete my test
-
55:30 - 55:32I just need to delete this guy
-
55:32 - 55:36and everything is back to green
-
55:36 - 55:37so now I'm using the parameter
-
55:37 - 55:40I made my refactoring
-
55:41 - 55:43(???) thing that I can do is
-
55:43 - 55:46this logged in user
-
55:46 - 55:49I probably don't need that anymore
-
55:49 - 55:55so if I replace this one by these guys here
-
55:56 - 55:59and I put this guy here
-
56:02 - 56:06so one way to know if
-
56:06 - 56:10so now for example, eclipse is saying that this variable is not being used
-
56:10 - 56:14so now I can delete that
-
56:14 - 56:17and delete that
-
56:17 - 56:19delete that
-
56:19 - 56:23and everything is green
-
56:23 - 56:26so this is a perfect place to commit again
-
56:26 - 56:29oh, by the way, there is a warning here
-
56:29 - 56:33so should always get rid of the unused import
-
56:33 - 56:35because rubbish thing around (???)
-
56:35 - 56:37so craftsman ship is important
-
56:37 - 56:39keep it clean
-
56:40 - 56:42so cool, that's a great place to stop
-
56:42 - 56:44so let's say
-
56:45 - 56:47git commit
-
56:50 - 57:01logged in user passed into trip service
-
57:02 - 57:04cool, awesome
-
57:06 - 57:09so maybe now we're done
-
57:11 - 57:13sort of, sort of
-
57:13 - 57:17I still don't like this thing in here
-
57:18 - 57:19this trip dao thing
-
57:19 - 57:21I don't like the statical
-
57:21 - 57:25this statical makes me do this thing that
-
57:25 - 57:30as I said this is sort of intermediately step
-
57:30 - 57:34ideally I would inject trip dao in there
-
57:34 - 57:36using an instance method
-
57:36 - 57:39but because it's static method, I can't do much
-
57:39 - 57:41I can't really inject that, right?
-
57:41 - 57:43cause this is always
-
57:43 - 57:46you mean normally inject an instance not class
-
57:46 - 57:48so it doesn't work
-
57:48 - 57:51so I want to go further
-
57:51 - 57:53I want go get rid of this
-
57:53 - 57:56I want to have a instance method in my trip dao
-
57:56 - 57:58so let's open my trip dao
-
57:58 - 57:59let's see what it does
-
57:59 - 58:00so of course that in this case
-
58:00 - 58:02it is just like a toy example
-
58:02 - 58:05so you just throw an exception
-
58:06 - 58:10as I said before in a normal circumstance
-
58:10 - 58:12this would go to database
-
58:12 - 58:13or to distributed cache
-
58:13 - 58:18or whatever persistence mechanism you have
-
58:19 - 58:21so what I want to do
-
58:22 - 58:29I want to create an instance method for that
-
58:30 - 58:35so I'm gonna write a test for it
-
58:38 - 58:39let's say
-
58:43 - 58:46trip dao test
-
58:47 - 58:49let's put into test
-
58:52 - 58:54so this is an interesting one
-
58:56 - 58:57it's an interesting trick
-
58:58 - 59:03so I want an instance method
-
59:03 - 59:09that does everything that the static method does
-
59:12 - 59:13same behavior, just
-
59:16 - 59:18you could say, well, just remove static method
-
59:18 - 59:23just remove the static keyword from there
-
59:23 - 59:24yes, I could
-
59:24 - 59:25but if I do that
-
59:25 - 59:28I'll need to fix my entire code base
-
59:28 - 59:32because, let's assume that there are many classes in my application
-
59:32 - 59:35that are using this static method
-
59:35 - 59:38as soon as I transform this one into instance
-
59:38 - 59:40I'll break entire code base
-
59:40 - 59:43and that'll be much bigger refactoring to do
-
59:43 - 59:45and we want to do everything in small steps
-
59:46 - 59:48so what I'll do
-
59:49 - 59:51I create a test, let's say
-
59:51 - 59:53I'll just mimic this behavior
-
59:53 - 59:55so don't take this test too seriously
-
59:55 - 59:57just get the idea of what I'm doing
-
59:57 - 60:01but I'll just represent what this method currently does
-
60:01 - 60:20so should throw exception when retrieving user trips
-
60:21 - 60:26so I want this to throw an exception
-
60:28 - 60:30as I said
-
60:30 - 60:34ideally this would go to the database or something
-
60:34 - 60:36I'm just preserving the same behavior
-
60:36 - 60:40so dependent class call during unit test blah blah
-
60:40 - 60:42so that's the exception thrown
-
60:42 - 60:45but I want an instance method
-
60:45 - 60:48so just invoking the method should be enough
-
60:48 - 60:49so what I can do
-
60:49 - 60:52new trip dao
-
60:55 - 61:06let's call it trips by user
-
61:10 - 61:13so I want an instance method
-
61:15 - 61:16that throws an exception
-
61:16 - 61:18so I'm gonna create this method
-
61:19 - 61:26I'm gonna make it return the same thing as the other one
-
61:33 - 61:35so my test is failing, of course
-
61:35 - 61:37so now what I want this to do
-
61:37 - 61:40it should behave exactly like this static method
-
61:40 - 61:43so what I do is trip dao dot
-
61:45 - 61:52I just invoke from the instance method
-
61:52 - 61:53invoke the static one
-
61:54 - 61:56why do I do that?
-
61:56 - 61:57the cool thing is that
-
61:57 - 62:03as I start moving my code to use this one
-
62:05 - 62:06so now what I'm gonna do
-
62:06 - 62:09I'm gonna replace the trip service used the instance method
-
62:09 - 62:11and imagine that I start doing that
-
62:11 - 62:13for all the other class that used static
-
62:13 - 62:19until there's no class referencing the static method anymore
-
62:19 - 62:20and what I can do
-
62:20 - 62:22I just can copy this behavior
-
62:22 - 62:24paste it into here and delete the static method
-
62:24 - 62:26and then I'm done
-
62:26 - 62:27so that's the trick
-
62:27 - 62:29so going back to
-
62:29 - 62:35so now my trip dao has the instance method
-
62:35 - 62:38so what I need to do now
-
62:38 - 62:44is to have a reason to inject this trip dao in there
-
62:44 - 62:46so now I'm gonna
-
62:46 - 62:49there are quite of few ways of doing that like for example
-
62:49 - 62:51I could use normal dependency injection with no framework
-
62:51 - 62:55just pass the trip dao into the constructor of the trip service
-
62:55 - 62:57for example, that's one way of doing it
-
62:57 - 62:59and that's the simplest way
-
62:59 - 63:03as I know that majority of people that use java
-
63:03 - 63:06they use spring and they use mockito and stuff like that
-
63:06 - 63:08I'll show you how it would work in mockito
-
63:08 - 63:11but for now, if you don't use mockito
-
63:11 - 63:12then that's it
-
63:12 - 63:17create a constructor pass the trip dao to the constructor
-
63:17 - 63:19I'm just gonna do with mockito and spring
-
63:19 - 63:22just to show what you can do basically
-
63:22 - 63:27so I want now to have a broken test
-
63:27 - 63:31that justifies me to inject a trip dao in there
-
63:31 - 63:33so, I'm gonna say run with
-
63:33 - 63:36I'm gonna use JUnit with mockito
-
63:36 - 63:37mockito runner
-
63:37 - 63:39junit runner
-
63:42 - 63:47so if you don't use mockito and the junit in spring (???)
-
63:47 - 63:48maybe a bit complicated
-
63:48 - 63:52so I recommend that you read the documentation of all these frameworks
-
63:55 - 64:01so far, I've been using the testable trip service
-
64:01 - 64:03that class we created before
-
64:04 - 64:07now I want to start using the real one
-
64:07 - 64:09the real one and injecting
-
64:09 - 64:13cause I don't want these thing here anymore
-
64:13 - 64:15I don't want this madness here anymore
-
64:16 - 64:19I want to just to be able mock to my dependency
-
64:19 - 64:21so I'll create another
-
64:21 - 64:24do that in very small steps
-
64:24 - 64:26trip service
-
64:27 - 64:37I'll call for now real trip service new trip service
-
64:37 - 64:38that's the real one
-
64:38 - 64:41I'm gonna create a mock
-
64:52 - 64:55I'm gonna create a mock to my dao
-
65:00 - 65:03I'm gonna use mockito, to say
-
65:04 - 65:06inject mocks
-
65:06 - 65:10and I'm gonna spy on it
-
65:11 - 65:21so basically it's out of scope for me to explain how mockito works
-
65:21 - 65:26but basically in very short explanation
-
65:26 - 65:28I'm creating the real class
-
65:28 - 65:30I'm asking mockito to spy on the real class
-
65:31 - 65:35and to inject all the mocks that this class may have
-
65:35 - 65:37I create the mock in here
-
65:37 - 65:41I just need to inform that this class is expecting this mock
-
65:42 - 65:44so but before I go there
-
65:44 - 65:47I'm gonna, now I'm using the real trip service in here
-
65:47 - 65:49I'm gonna where it breaks
-
65:49 - 65:49so for example
-
65:49 - 65:51I can go bit by bit, I can say
-
65:51 - 65:55real trip service and save
-
65:56 - 66:00so my test still green
-
66:01 - 66:03so because what I'm trying to do
-
66:03 - 66:06I'm trying to get rid of this guy now
-
66:06 - 66:07bit by bit
-
66:08 - 66:09to replace with a real one
-
66:10 - 66:11so real trip service
-
66:11 - 66:13so if I do that
-
66:14 - 66:16it works, cool
-
66:16 - 66:17the reason it works
-
66:17 - 66:20because none of those test that use, they go the dao
-
66:20 - 66:22none of them get to this line
-
66:24 - 66:31so this one, real trip service
-
66:32 - 66:35so now it breaks, right?
-
66:35 - 66:37that's what I was looking for
-
66:37 - 66:39now it breaks because
-
66:39 - 66:44I'm using in the test the real trip service
-
66:44 - 66:48and that calls the static method in the trip dao
-
66:48 - 66:49and that throws an exception
-
66:49 - 66:51so cool, now I have a broken test
-
66:51 - 66:54so I have a reason to change my production code
-
66:57 - 66:59so what I'll do
-
67:00 - 67:02I'll go to my production code and say
-
67:02 - 67:04if I'm using spring
-
67:04 - 67:06auto wired
-
67:18 - 67:22so just import the auto wired
-
67:22 - 67:25so imagine that if you gonna define the trip dao in a spring bean
-
67:25 - 67:27in a xml
-
67:27 - 67:32so private trip dao trip dao
-
67:32 - 67:33awesome
-
67:35 - 67:37maximize that
-
67:38 - 67:40so cool
-
67:40 - 67:42so what mockito will do
-
67:42 - 67:44mockito will take this guy here
-
67:44 - 67:47mymock and inject into this guy here
-
67:49 - 67:52so now I need to start using that
-
67:52 - 67:55so my test is broken
-
67:55 - 67:57and I need to start using that
-
67:57 - 68:02so in here, instead of using the
-
68:05 - 68:07so if I run, I just run a test
-
68:07 - 68:10just to show you the difference
-
68:29 - 68:31run as
-
68:33 - 68:35I keep pressing wrong button
-
68:35 - 68:40so it's due to using the real dao, right?
-
68:40 - 68:45so what we're gonna do is replace with the instance
-
68:45 - 68:49and say use the instance method
-
68:49 - 68:51instead of this classic one
-
68:52 - 68:56so now if I run my test again
-
68:56 - 69:00the error changed
-
69:00 - 69:03so basically it's now using my mock
-
69:03 - 69:07so if I just minimize it again
-
69:07 - 69:12so now it's using my mock
-
69:13 - 69:18and it says that the test expects two trips but it got zero
-
69:18 - 69:20so that's what my test does
-
69:20 - 69:22my test's expecting two
-
69:24 - 69:26so what I need to do is
-
69:26 - 69:28the reason that this is happening
-
69:28 - 69:31because now my production code is using the mock
-
69:31 - 69:33and the mock is not configured
-
69:33 - 69:38so I just need to say that
-
69:41 - 69:44given my trip dao
-
69:44 - 69:49dot trips by user
-
69:52 - 69:58so given that this method is invoked in my mock
-
69:59 - 70:05it will return friend dot trips
-
70:05 - 70:08so what I'm doing here
-
70:08 - 70:16I'm configuring my mock to return the friend trips
-
70:16 - 70:19exactly as the way we've done here
-
70:19 - 70:22I'm doing in here just configuring my mock
-
70:22 - 70:25so now as you can see my test is green
-
70:27 - 70:31so this guy can go back to private now
-
70:33 - 70:37and I don't need this method anymore
-
70:39 - 70:41and if I run all my tests
-
70:41 - 70:43my tests are all green
-
70:43 - 70:46so that means I don't need this class anymore
-
70:48 - 70:51so that means that I don't need this
-
70:51 - 70:54and if I don't need this, I don't need this
-
70:54 - 70:57and in this case, this guy is not been used anymore
-
70:57 - 70:59I can delete
-
71:00 - 71:02so now I can remove
-
71:02 - 71:04I can rename my real trip service
-
71:04 - 71:06because real means nothing
-
71:06 - 71:07so when we're doing test
-
71:07 - 71:14I've seen people calling variable like testee or mock (???)
-
71:14 - 71:15call it what it is
-
71:16 - 71:20it's a trip service. so it should be called trip service, right?
-
71:20 - 71:23so now it's great because
-
71:24 - 71:27I don't have that madness of test double class anymore
-
71:27 - 71:30my test reads quite well
-
71:31 - 71:35and so just make sure that everything is green
-
71:35 - 71:38I can even go there and say
-
71:44 - 71:46so validate logged in user
-
71:47 - 71:49so now as I said
-
71:49 - 71:54I can close all my test methods
-
71:55 - 72:00and we can read this block here and say
-
72:00 - 72:03should throw an exception when user is not logged in
-
72:03 - 72:05that's part of the validation of the logged in user
-
72:06 - 72:09should not return any trips when users are not friends
-
72:09 - 72:13so if the users are not friends returns no trips
-
72:14 - 72:18so should return friend trips
-
72:18 - 72:19when users are friend
-
72:19 - 72:22so if the user is friends with the logged in user
-
72:22 - 72:24just return the trips
-
72:28 - 72:31even this getter method here is not great
-
72:31 - 72:36so get trips by user so that could say
-
72:36 - 72:39get friend trips
-
72:40 - 72:42that sounds much better
-
72:42 - 72:46this guy could be
-
72:49 - 72:51friend as well
-
72:52 - 72:54or something like that
-
72:56 - 72:59so you can play with these things now
-
72:59 - 73:02that you have all the code coverage
-
73:03 - 73:07so basically that's another great point to stop
-
73:07 - 73:09so git status
-
73:10 - 73:12git add .
-
73:15 - 73:18so git commit
-
73:19 - 73:27trip dao injected into trip service
-
73:28 - 73:31so this it it
-
73:31 - 73:32that's normally
-
73:33 - 73:35that's what all the things that you can do
-
73:35 - 73:37with legacy code in a very safe way
-
73:39 - 73:41I can show you
-
73:47 - 73:50I have original trip service somewhere in here
-
73:50 - 73:54so you can compare
-
73:54 - 74:03so we went from this to this
-
74:03 - 74:06and of course that
-
74:06 - 74:09it took me just an hour to do that
-
74:09 - 74:13but it's because I'm recording this screen cast
-
74:13 - 74:16but as soon as you're comfortable with all these techniques
-
74:16 - 74:20this probably would have taken me in a normal day like 20 minutes
-
74:20 - 74:22so it's just like
-
74:22 - 74:24as soon as you're aware of what is available to you
-
74:24 - 74:26then you just practice it
-
74:26 - 74:29and then it makes things much easier
-
74:29 - 74:32and just as a reminder
-
74:32 - 74:35every time that you're working with legacy code
-
74:35 - 74:40your first test should never be the one that goes to the deepest branch
-
74:40 - 74:42always start from shortest branch
-
74:42 - 74:44from the shortest to the deepeset
-
74:44 - 74:47write your test in this order from the shortest to the deepest
-
74:47 - 74:51it's easier because the test will help you to understand what the code does
-
74:51 - 74:55and it will help you to build the test data
-
74:55 - 74:58bit by bit as you dive deep into the code
-
74:58 - 75:00when you're refactoring
-
75:00 - 75:01do the other way around
-
75:01 - 75:03find the deepest branch
-
75:03 - 75:05and start extracting method from there
-
75:05 - 75:08starting put in that behavior into different classes
-
75:08 - 75:11and then all of sudden your code will start
-
75:11 - 75:14collapsing and getting smaller and smaller
-
75:14 - 75:17because you're removing the internal parts
-
75:19 - 75:24so try to write readable and maintainable code
-
75:24 - 75:26try to have the domain language in there
-
75:26 - 75:28try to capture that
-
75:28 - 75:30like using things like guess(???) and stuff like that
-
75:30 - 75:32so as part of the domain
-
75:32 - 75:37try to bring the domain language into your test and into your code
-
75:37 - 75:43the language spoken in the test and the code and used it with your BA should be the same
-
75:43 - 75:46try to have simplicity
-
75:46 - 75:49know your shortcuts, frameworks like
-
75:49 - 75:51try to be fast
-
75:52 - 75:56configure your environment so you can with a few shortcuts
-
75:56 - 75:58create test methods and classes and all sort of stuff
-
75:58 - 76:00and do the refactorings
-
76:00 - 76:04and as I said working in very small increments
-
76:04 - 76:05commit often
-
76:05 - 76:07I mean if you're using git and stuff like that
-
76:08 - 76:10because if you make mistake
-
76:10 - 76:13Ctrl+Z or git reset hard
-
76:13 - 76:15you go back to previous commit
-
76:15 - 76:17and you're back into the green
-
76:17 - 76:19be brave
-
76:19 - 76:22now you know a lot of things like
-
76:22 - 76:27all these small tricks that we learned today
-
76:27 - 76:31we'll make probably 99% of all your classes testable
-
76:31 - 76:33so just be brave
-
76:33 - 76:36use it, and
-
76:37 - 76:40always leave the campground cleaner than you found it
-
76:40 - 76:41so that's the boy scout rule
-
76:41 - 76:43no broken windows
-
76:43 - 76:44if it's smelly
-
76:44 - 76:48if it's not very well written
-
76:48 - 76:49just refactor it
-
76:49 - 76:51just go for it
-
76:51 - 76:53yep, so I hope you enjoyed it
-
76:54 - 76:56that's it
-
76:56 - 77:01so you can find this trip service kata in my github
-
77:01 - 77:04so just go to github sandromancuso trip service kata
-
77:04 - 77:06that's it, hope you enjoyed it
-
77:06 - 77:07thank you
- Title:
- Testing and Refactoring Legacy Code
- Description:
-
In this video, we take a piece of crappy Java code with no tests. Our objective is to write tests for it and then refactor to make it better. The code has the most common problems that much larger legacy applications have, like Singletons, static calls and feature envy. It also has some design problems. Fixing that is quite hard, mainly when we need to write all the tests before we start the refactoring. Another rule: We cannot change production code if it is not covered by tests but quite often we need to change the production code in order to be able to test it. How to solve this problem? Well, I hope I can answer all these questions in this video.
If you want to know more about Software Craftsmanship, please check my book: https://leanpub.com/socra
If you want to play with the code, clone it from: https://github.com/sandromancuso/trip-service-kata
- Video Language:
- English
- Duration:
- 01:17:08
![]() |
YongWook Kim edited English subtitles for Testing and Refactoring Legacy Code | |
![]() |
YongWook Kim edited English subtitles for Testing and Refactoring Legacy Code | |
![]() |
YongWook Kim edited English subtitles for Testing and Refactoring Legacy Code | |
![]() |
YongWook Kim edited English subtitles for Testing and Refactoring Legacy Code | |
![]() |
YongWook Kim edited English subtitles for Testing and Refactoring Legacy Code | |
![]() |
YongWook Kim edited English subtitles for Testing and Refactoring Legacy Code | |
![]() |
YongWook Kim edited English subtitles for Testing and Refactoring Legacy Code | |
![]() |
YongWook Kim edited English subtitles for Testing and Refactoring Legacy Code |