Getting a Handle on Legacy Code
This is a modal window.
Das Video konnte nicht geladen werden, da entweder ein Server- oder Netzwerkfehler auftrat oder das Format nicht unterstützt wird.
Formale Metadaten
Titel |
| |
Serientitel | ||
Teil | 49 | |
Anzahl der Teile | 94 | |
Autor | ||
Lizenz | CC-Namensnennung - Weitergabe unter gleichen Bedingungen 3.0 Unported: Sie dürfen das Werk bzw. den Inhalt zu jedem legalen und nicht-kommerziellen Zweck nutzen, verändern und in unveränderter oder veränderter Form vervielfältigen, verbreiten und öffentlich zugänglich machen, sofern Sie den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen und das Werk bzw. diesen Inhalt auch in veränderter Form nur unter den Bedingungen dieser Lizenz weitergeben. | |
Identifikatoren | 10.5446/30665 (DOI) | |
Herausgeber | ||
Erscheinungsjahr | ||
Sprache |
Inhaltliche Metadaten
Fachgebiet | ||
Genre | ||
Abstract |
|
RailsConf 201549 / 94
1
4
7
8
9
10
11
13
14
16
17
19
21
24
25
29
30
33
34
35
36
37
39
40
42
47
48
49
50
51
53
54
55
58
59
61
62
64
65
66
67
68
70
71
77
79
81
82
85
86
88
92
94
00:00
RichtungPunktTypentheorieTreiber <Programm>NavigierenSystemaufrufDeskriptive StatistikProdukt <Mathematik>Kartesische KoordinatenNebenbedingungMultiplikationsoperatorKlasse <Mathematik>SoftwaretestElektronischer ProgrammführerLaufzeitfehlerSchlussregelPhysikalisches SystemBitIdentifizierbarkeitMathematikGleitendes MittelAlgorithmusKontrollstrukturRuhmasseCoxeter-GruppeWort <Informatik>Demo <Programm>DatenstrukturÜberlagerung <Mathematik>TaskKategorie <Mathematik>TouchscreenSchedulingProgrammierungAutomatische HandlungsplanungSchnitt <Mathematik>Ebener GraphProgrammiergerätAnalysisParametersystemVererbungshierarchieSprachsyntheseRechter WinkelZahlenbereichOrdinalzahlZellularer AutomatProzess <Informatik>DatenverwaltungMaschinenspracheStapeldateiAppletExogene VariableGruppenoperationRechenschieberAggregatzustandTermersetzungssystemElektronische PublikationReelle ZahlCOMSchnelltasteMaschinencodeGemeinsamer SpeicherMaschinenschreibenMereologieLeistung <Physik>Computeranimation
09:00
MinimumMaschinenspracheGewicht <Ausgleichsrechnung>SoftwaretestSoundverarbeitungBitTouchscreenUniversal product codeDatenbankKlasse <Mathematik>StandardabweichungVariableInstantiierungMereologieRechter WinkelGüte der AnpassungPunktShape <Informatik>Produkt <Mathematik>Patch <Software>EinsMathematikParametersystemEin-AusgabeKartesische KoordinatenDatenstrukturRechnernetzUngleichungAlgorithmusRefactoringDreieckOrdinalzahlKategorie <Mathematik>Formale SpracheFlächeninhaltStichprobenfehlerBildschirmmaskeToter WinkelComputeranimation
14:16
SoftwaretestTouchscreenMultiplikationsoperatorRepository <Informatik>MathematikGüte der AnpassungFunktion <Mathematik>ZweiProdukt <Mathematik>ErwartungswertTexteditorZellularer AutomatMaschinenspracheProgrammierungZahlenbereichGarbentheorieRefactoringPunktBitDifferenteKlasse <Mathematik>Message-PassingMusterspracheCASE <Informatik>QuellcodeParametersystemGlättungPhysikalisches SystemVollständigkeitÜberlagerung <Mathematik>RPCWasserdampftafelQuadratzahlSchnelltasteRechter WinkelComputeranimation
19:28
Elektronische PublikationWort <Informatik>MathematikFehlermeldungMultiplikationsoperatorFontMini-Discp-BlockSelbstrepräsentationProgrammverifikationEinfache GenauigkeitRechter WinkelTopologiePhysikalische TheorieOntologie <Wissensverarbeitung>Treiber <Programm>ComputeranimationVorlesung/Konferenz
20:54
FaserbündelRechter WinkelFunktion <Mathematik>Profil <Aerodynamik>GeradeProgrammverifikationDifferenz <Mathematik>GruppenoperationGraphfärbungArithmetische FolgeFlächeninhaltComputeranimation
22:11
SoftwaretestBildschirmfensterBitTypentheorieGeradeSelbstrepräsentationCASE <Informatik>Zellularer AutomatGraphfärbungZeichenketteMultiplikationsoperatorFunktion <Mathematik>ZoomPunktMaschinenspracheData MiningFehlermeldungMereologieNabel <Mathematik>ProgrammverifikationEinsInterpolationRefactoringEindringerkennungZweiObjekt <Kategorie>VerzeichnisdienstDifferenteIntegralRadikal <Mathematik>KreiszylinderEndliche ModelltheorieEnergiedichteParametersystemDualitätstheorieAutomatische IndexierungSchreiben <Datenverarbeitung>TopologieDatensatzRuhmasseInformationKontrast <Statistik>FlächeninhaltLoopRechter WinkelGruppenoperationComputeranimation
28:58
CASE <Informatik>SoftwaretestGeradeFehlermeldungOrdnung <Mathematik>MultiplikationsoperatorDienst <Informatik>Eigentliche AbbildungNebenbedingungDifferenteGewicht <Ausgleichsrechnung>Elektronische PublikationBefehl <Informatik>MaschinenspracheTopologiePunktVerzeichnisdienstUniversal product codeAggregatzustandLoopGreen-FunktionSchnittmengeWurzel <Mathematik>SchlussregelSystemaufrufExtreme programmingBitFlächeninhaltWechselsprungBereichsschätzungTwitter <Softwareplattform>AnalogieschlussKontrollstrukturRefactoringIntelligentes NetzProzess <Informatik>SoftwareentwicklerMathematikProdukt <Mathematik>ComputeranimationVorlesung/Konferenz
35:46
SchlussregelSiedepunktMereologieFaktor <Algebra>Kartesische KoordinatenFreewareMultiplikationsoperatorQuick-SortRefactoringBitHackerFunktionalMathematikSoftwaretestSystemaufrufDivergente ReiheProzess <Informatik>RichtungProgrammierungProdukt <Mathematik>Güte der AnpassungBrowserGruppenoperationBildschirmmaskeMaschinenspracheGefangenendilemmaTermersetzungssystemDifferenteRechter WinkelComputeranimationVorlesung/Konferenz
38:56
Verzweigendes ProgrammTaskMusterspracheMaschinenspracheMaschinencodeElektronische PublikationRadikal <Mathematik>VariableFunktionentheorieAutomatische IndexierungSpeicherabzugBitGruppenoperationLoopKlasse <Mathematik>MereologieSoftwaretestInstantiierungsinc-FunktionRichtungFokalpunktMechanismus-Design-TheorieMathematikPunktRefactoringGeradeSchnelltasteSystemaufrufRauschenÄquivalenzklasseRechter WinkelMultiplikationsoperatorSelbst organisierendes SystemFaktor <Algebra>VererbungshierarchieFormale SpracheDifferenzkernInelastischer StoßBeobachtungsstudieOrdnung <Mathematik>SchnittmengeIterationWort <Informatik>AbstraktionsebeneArithmetischer AusdruckEinfügungsdämpfungTrennschärfe <Statistik>GrundraumGreen-FunktionGraphfärbungVollständiger VerbandPartikelsystemComputeranimation
Transkript: English(automatisch erzeugt)
00:13
All right, good afternoon. Thanks for coming to my workshop today. I hope you'll enjoy it. I hope I can keep you entertained enough to stay out of the post-lunch food coma
00:22
that I have going on too. So I've noticed in the room, this side's darker, this side's brighter, and so there's more glare on the screen on this side, so it might be a little harder to read the code on the screen. But if you like being in the light, that's great. I'm gonna be sitting for a lot of this, and you won't see me past the podium, which is either a downside or a bonus. I'm not that pretty to look at,
00:40
so you might wanna sit over there. So I'll give you a minute to move around if you want. There's instructions for the lab up on the screen. We won't need that for a little while, so you'll have some time to get that going, and if the Wi-Fi goes down, we'll have to live with that, I guess, sorry. So I'd just like to get a sense. How many of you consider yourselves relatively new Ruby programmers, like maybe less than a year?
01:02
A few of you? And everyone else fairly experienced? I just kinda wanna know how much explanation we need to do on some of the stuff that we go through. Also, I'll try to explain things as we go. If you have questions, stop and ask. That'd be great. All right, so how many of you
01:20
have had to deal with legacy code before? A lot of you. How many of you have written legacy code yourself? That'd be me too. I think we all have at some point. So this workshop is based on a code kata called the Gilded Rose Kata. You can look it up online. It's a really cool kata, I love doing it.
01:41
It's plain Ruby code. I'm not gonna get into Rails here. You know, it's RailsConf, I know. But it's an exercise that's like bite-sized enough that we can run with it during the workshop a little bit. We probably won't get done. This is an hour and a half workshop. The schedule posted around the conference is a little bit unclear, but we actually end at 3.20, so just before the last talk slot.
02:00
I'm happy to stay around after and ask questions. There's nobody else in the room after us, so we can stay late if you want, but I'm gonna plan to wrap up for 3.20, just so you can get to that last talk if you were planning to. And if you wanna hang around, I'd be happy to do that, although my colleague, Adam Cuppy, is speaking right after me, and I kinda wanna see his talk, so. But I can see it some other time if I have to. So anyway, so Gilded Rose Kata.
02:21
We're starting a new job today, and we're working for the Gilded Rose Inn, and we're working on the inventory management system at the inn. If you play World of Warcraft, you recognize a lot of the references in this code. You'll probably have fun with it. So in our system, we have a bunch of items that we stock and we sell, and they all have a sell-in value. The number of days that we have to sell the item.
02:43
All the items have a quality, and that quality degrades over time. So that's how valuable the item is. And our system runs kind of like a batch process at the end of the day, and updates both values for all of the items in the inventory. There is a spec. You do not need to read this wall of text.
03:01
Just know that there is one. We have no clue if we can trust it. Documentation lies, right? You come onto a system where the spec was written months or years ago. The code may or may not reflect that. So this might give us a bit of an idea what the system's about and some of the rules in the system, but we can't trust it. We don't know if it's true. We don't know if the code matches it. So we can use it as a rough guide,
03:21
but you don't wanna put too much reliance on it because somebody's gonna pull that out from under you at the wrong time. We have a constraint. There's a troll that works on the item class, and he does not share code ownership of his code. The actual account description says that if you change this code, he will one-shot you, and you're dead. So we cannot touch the item class or the item's property.
03:44
So that's a constraint. There's no collective code ownership on this team. This is not a workshop about how to deal with problem people on your team, so we won't cover that today much as we probably would in a real team, okay? Now, with legacy code, if it's running and working and you don't need to add features,
04:01
there's no reason to touch it. So we're done. We can go. We have a new task. There's a new kind of item we're gonna support called conjured items, and conjured items degrading quality twice as fast as normal items. So that's what's special about them, and that's what we have to support in our system. So the way I'm gonna structure this workshop, we'll do a little bit of presentation and demo.
04:22
This is probably the most I'll talk at once in the whole workshop. We're gonna do some mob programming, and we're gonna do some small group pair individual work. I'm gonna really kinda play it by ear between those two if the mob program is working and you guys are liking it, we'll keep doing it. If it's not working and you wanna kinda work on your own, that's cool too.
04:40
If you wanna just take this Kata, take the code and run with it for a while on your own and ask questions, I'm cool with that too. It's a really fun Kata to work on. I love playing with it. But we're gonna start with mob programming. And so the way mob programming works, I'm not sure if you've heard of it, a guy by the name of Woody Zwil, I'm not sure I'm pronouncing his name right, kind of has been popularizing it a little bit.
05:00
And the idea is, so pair programming, you have a driver role and a navigator role, so the person with the keyboard and the mouse is driving and the other person is navigating strategic direction, that kind of thing. With mob programming, you have one driver and everyone else is either a navigator or a researcher. So if you need to look up an API call, somebody can have their computer and look it up. But there's one person actually typing the code
05:21
and that person is mostly typing what the other people are telling them to type. Oh, let's do this and let's do that. And so I'm going to be the driver for this just because having to get different people up here working on my setup is probably not gonna work well. And you're all gonna tell me which direction to go. I'm gonna be giving some guidance because I have a few points I'd like to get across this,
05:41
but it's gonna be pretty organic and really driven by the ideas that you come up with. I am gonna try to steer things in certain directions. So how do we safely change legacy code? Anytime you have to touch legacy code, that's your question. It's like, I have to change this stuff, but this stuff's running in production,
06:02
it's meeting somebody's needs, it's maybe making my business money or it's helping people, whatever your application is doing. For all that legacy code is gross and nasty, it is working, mostly. It is doing something that is valuable for somebody and you can't afford to break that. So as much as you wanna diss on the code
06:21
when you look at it, remember that it's actually providing value, there is something there and you have to respect that. And so a lot of people just say, hey, I'm just gonna rewrite this mess. There's a lot of blood and sweat and tears that went into that code and a lot of learned lessons that are hidden away in the mess and when you do a rewrite, you forget about that stuff. So we need to respect that this code
06:41
is actually doing something. We need to be safe about changing it. If you work with legacy code, you need this book by Michael Feathers. It's called Working Effectively with Legacy Code. It's a few years old now, but it is gold. It is pure gold. There's so many good techniques in there. So in that book, he gives the legacy code algorithm, which we'll be following. The first thing you do is you identify change points.
07:02
So you have a feature you need to add. You need to figure out what can you change in the code? Where does this change need to happen? And then you need to find places where you can test. The way we safely change legacy code is we get tests around it. I should mention Feathers' definition of legacy code is code without tests.
07:21
The definition I like more is code that I didn't write, but really I think his is a little more politically correct, so we'll go with his. So you need to find test points. Then you need to break dependencies because a lot of times the stuff you need to test is too tightly coupled to everything else in the system and you really can't get at it. Then you write the tests. Then you make the changes and refactor the code.
07:43
So I actually tweeted at Michael the other day because I was curious how he thought this book applied to something like Ruby because Ruby, you have things like metaprogramming and monkey patching, which, I mean, he was writing more about C++ and Java stuff, which don't have those tools. And so his response was you put more effort
08:01
into characterization testing than dependency breaking. The latter is trivial. So in Ruby, the dependency breaking is less onerous because you have power tools, but you still need to do some of it. But the characterization testing that we'll be talking about in a minute is the more important part. So I want to thank Michael for responding to me. That was cool. I had a slide up earlier. Here's the code for the workshop.
08:22
You can clone that if you want to if you haven't already. A couple of people were having problems with a couple of things, so I actually did just push up a change. I forgot to include rake in the gem file. So it's there if you're having a problem with that. But let's get to looking at the code. And before we do that, OSHA requires me to show you this warning. That's the Occupational Health and Safety Administration
08:41
if you're not from the States. I have to show you this warning because the code is really bad. All right, so the first thing we're gonna do is we're gonna do some mob programming and we're gonna write some characterization tests for the code. So let's, I'm gonna sit down now and we'll get into the code and take a look at it.
09:06
How's that font size in the back? Is it readable? Yeah? Awesome. Okay. And the color on white is good? Bigger? Bigger, yep. So unfortunately RubyMine does not respond to Command Plus
09:21
so I have to do it the hard way. Oh, cool. How's that? I wanna be able to get some code on the screen here, so I'm gonna maximize that a little bit. Is that better? Yeah.
09:41
Okay. So this is the item class that we're not allowed to touch. How much of that's shown on your screen? I should be all right. Okay, so we just wanna look at it and see what it's about. You see an item has a name, a cell, and a quality, just standard adder accessor, so this is essentially a struct and not much more than that. So like I said, we're not allowed to touch that because of the troll in the corner
10:00
that we're not dealing with today, but it's there and that's what it does. Let's look at the tests. That's all the tests we have to start with. This is how the kata comes. So it actually calls a method, but I don't see any checks in there. There's no shoulds, no expects, no nothing. So great, legacy code.
10:23
And here's the code. So there's this Gilded Rose class. It has, we have our database hard coded right in the initialize method, that's awesome. And then there's this update quality method that I will just scroll through and scroll through.
10:40
So don't try to study that yet. It's nasty. I call this wedgie code because of the shape of it, all the wedges and triangles, whatever you want to call it. Okay, so that's what we're working on today. So the first thing we need to do, according to the legacy code algorithm, is we need to identify change points. So just kind of skimming through this code, what part does it look like we might have to change
11:01
to be able to support conjured items? Update quality, that looks like a pretty good change point. Notice in our items collection, there is actually a conjured item already. We're gonna guess that that's probably not being handled correctly yet. We don't know that, but probably not. But there is actually a conjured item
11:21
in the database for us to work with, which is good. But update quality is our change point. So now we have to identify a test point. So what can we test about this method? There are no input parameters. We scroll down to the bottom.
11:40
There's not really a return value. So what can we test about this method? What's our test point? Items property, yeah. Pretty much this whole method operates by side effects on the items collection. Okay, there is no getter for the items collection. So how are we gonna get at that?
12:02
There's basically at least three things I can think of. So the first obvious one is, well, let's just add an add a reader, right? Let's just drop that in up top like that. The problem is we just changed production code and we have no tests for it. Now sometimes that's what you have to do with legacy code
12:20
because there just is not another option. But this is Ruby and we have power tools. So let's not do that. I don't wanna touch the production code until we have a safety net to work with. Yeah, instance variable get is the second way we can do it. And the third way, so I'm just gonna make a let for the items because that's what we're gonna be working with here.
12:40
And we'll play with some ideas. So one of the ideas was instance variable get. So that we can do, if I could spell, that would be great. So we can do that. And there's one other way we could do this that we won't go with,
13:01
but we could actually reopen items and we could monkey patch it only for the tests. We could do that. So Ruby gives us some tools, whereas in other languages, we'd probably be forced to change some production code without the test safety net.
13:20
So for today, we're gonna go with the instance variable get because I think that's probably the lowest impact one. But now we have the items. So there's our, what Michael Feathers calls these are sense points, things that you can sense about the application that you can actually write some assertions about. So that's great. So what's the first thing
13:40
we should maybe test on this code? We don't wanna study it. We don't wanna actually, we don't know what it does yet. It's unfamiliar. All we're trying to do is get a safety net. So we wanna go really quick and dirty. The kinds of tests we're gonna write here are not the kinds of tests you've been taught to write your whole career. These are gonna be ugly and messy and you're gonna throw them away. Coraline Emke did a great talk at Mountain West Ruby
14:02
and part of her talk she was talking about some of the testing you do on legacy code and you basically write, you can write nasty, ugly tests and once you get the code refactored so where you can write better tests, then you throw the ugly ones away. They're just there scaffolding to where you need to be. So we're probably gonna do some anti-patterns and some gross stuff with the tests
14:20
and that's okay because it gets us to where we need to be. So what might we wanna test first? What's an idea? Okay, whether or not the items change in quality is good. I wanna go even simpler than that.
14:40
Why don't we even just test that we can actually get the information out of the items in the first place? So change nothing, run nothing, just make sure that we can assert things about the items as they stand today. Does that sound okay? All right, so a lot of this talk is gonna be, I'm gonna be kind of insisting we go smaller, smaller baby steps because once you know how to go really small,
15:01
then you can handle any situation. Most of the time you won't go that small but when you need to, you really can. That's a point that Kent Beck makes a lot when he talks about programming. It's just sometimes you really need to go really small baby steps just to get through a tricky section and then you can kind of take bigger steps later. All right, so all we're gonna do is we're gonna try to write a quick spec about the items.
15:23
So I'm gonna actually get rid of the test that's there that's a complete no op, assuming my, learn how to drive a keyboard today. So I'm going to do, these are gonna be, actually I'll just specify here. So after no updates,
15:43
and these are gonna be ugly RSpec tests and that's okay. I'm using RSpec. I actually have many tests in the repo if you prefer many tests. I have a specific reason for using RSpec for this that we'll get to in a few minutes but I'm kind of, I switched between the two a lot. I haven't decided which one I like the best yet.
16:01
So let's just assert something about like the first item and that its quality hasn't changed. So let me split screen here real quick so that we can see both. I'm using RubyMine because I like the refactoring tools in it. Use whatever editor you like. All right, so let's just take the first item
16:22
and make sure that it's, I have to remember the order, cell in is first then quality. So let's just make sure it's,
16:42
we'll just write a couple of expects. They're gonna be ugly and that's okay because we're gonna change them in a minute anyway. I got these backwards, didn't I? It's cell in first and quality second, right?
17:05
All right, so let's go ahead and run that spec. Okay, so it passes. We can actually sense the items now. Now you can imagine what these tests are gonna look like if we keep writing them like this. And so instead of continuing down this road and spending our whole workshop on it,
17:21
I wanna teach you about a different style of testing that works really, really well for this kind of legacy code. Do we actually care about these numbers in this test? Do they matter at all right now? Do we care that they're right? Not at all. We just care that we don't change them. And so there's a style of testing. It's actually an anti-pattern, but for legacy code, it works great.
17:41
So let me just flip back to keynote real quick. One second. Here we go. All right, so it's called golden master testing, or it's also known as the guru checks output anti-pattern. And so what the idea is that you get some output from a system, you have a human look it over and say,
18:01
yep, that looks good. You save that off. The next time you run the test, you get the output out again. You compare the two. If they're the same, great, test passed. If they're different, then the test failed and the human has to look at the output again. Now the reason this is an anti-pattern is that the time you most need this test to be right is probably when you're making a change
18:20
that you need to get into production to fix a critical bug and at that point, the humans involved are gonna be the most stressed they're ever gonna be and so to have them manually check the output at that time, really bad idea. It's a recipe for disaster. That's why this is an anti-pattern. But in this case, we're working with legacy code. We don't care if the output's right. We just care that we don't change it.
18:40
And so golden master testing is awesome. We don't even have to look at the output to see if it's right. We just need to save it off as this is the master. If I change it, something broke, I need to back up and go again. So we're gonna use this for this and Katrina Owen wrote a gem called Approvals for this. I have to give a huge shout out to Katrina because I did a run through of this lab at a community college class last week
19:01
and there was just some things with the command line interface that I thought could be improved. So I spent Friday, submitted like four pull requests to the gem. This is like last Friday and that night, she merged all four of them and I thanked her for it and I said, that'll come in real handy in my workshop and she goes, oh, you're using this next week? I'll cut a release for you right now. So Katrina is awesome.
19:21
Thank you, Katrina, if you're watching this. So we're gonna use her gem because it's really cool. So let's keep mob programming a little bit and we're gonna bring in Approvals. It's already in the gem file. And so the way it works, it actually interfaces really nicely with RSpec. So we're just gonna require it in, okay?
19:45
And I'm gonna change this after zero updates test. Actually, I'll keep it running but I'm gonna just throw an old on the end of it because I wanna write some new stuff using Approvals. So we're gonna specify after zero updates. And so Approvals has a method called verify
20:03
that's kind of the main method that you use and it takes a block and all we're gonna do is verify the items and what it's gonna do is it's gonna save some representation of the items out to a file on disk and remember that is the golden master. And that's all we have to do to use this. So I'm gonna go ahead and run this test.
20:22
And it failed and, sorry, I wanted to blow that up. I do know how to drive RubyMine, I really do. Sorry, this font is really small. That is not right. I should've been getting a different, okay.
20:43
These are just warnings, I guess. So here's the actual error, it's an approval error because I don't actually have a golden master copy yet. And that's expected the first time. So I'm gonna flip over to a, not that, sorry. There we go, to a shell.
21:01
And so what we can do is we can run bundle exec Approvals verify. And so it's showing me a diff of the current approved copy and the new updated, or she calls it received copy and it's added a bunch of lines and we can say whether we wanna approve that or not.
21:21
So we're being the guru who's checking the output right now. And we're gonna say, yep, looks good, yes, go. Yep, would that be better here? Okay, yeah, it's my turn.
21:42
That's okay, I can flip it real quick. Is there a profile here? Yeah, there we go. Light background.
22:06
Yeah, close it and reopen it, okay. There we go. Is that better? Yeah, those colors aren't great, are they? All right, we won't be spending much time in the shell. So anyway, so.
22:24
Okay. Oh yeah, we accepted that one, so it's good. And we go back to review mine and we rerun our test. It's still, oh, that's interesting. So I rerun the test after we accepted the golden master and we're still getting an approval error.
22:43
It's not matching. See if I can grow the text on here, cool. Okay, and so let's go back and run Approvals verify again. Okay. So the difference is these object IDs in here. Every time you run this, you're gonna get different object IDs for the items.
23:01
So we actually need to have our golden master output be a little bit less variable than this. So we need to do a little bit more work here. So I'm gonna say, no, I don't wanna approve that. And we'll flip back to review mine and we'll just tweak our test just a little bit. So what I'm gonna do, zoom this particular window,
23:24
let me get rid of some stuff here. Okay, so instead of just getting the items, I'm gonna actually map that and just produce a string representation instead.
23:48
And this will just be really simple. We'll make it a little bit human readable so that when we are actually looking at these, they make some sense. So I'm just gonna do a really simple, I will push this up to GitHub so you can pull it down when we start working on this on our own, by the way,
24:01
so you don't have to try to type everything I'm typing. All right, so we're just gonna do a little string interpolation and get a string representation of each item in an array
24:20
and let approvals work with that instead. So run that again and again, we're getting an approval error and we run our verify. All right, so now this time, we've got a little bit cleaner output that's not likely to change every time we run the test. So let's approve that as our new golden master. And we rerun the test. Why are you so red?
24:46
Oh, undefined method, sell in for name. What did I do wrong? What's that? Oh, that's right too. Right, we have our new test, we can get rid of the old one. Thank you. There we go, green bar, okay. So now we have a test.
25:02
It's not actually calling the code at all, but at least we know we can get the items out and we can run the test repeatably and it's not failing every time we run it, which is good. That's a good first step, especially with legacy code, sometimes it can take you a week to get to this point, honestly. So that's cool. So let's do an actual update here.
25:22
This is gonna get a little repetitive, but we'll fix that in a second. So the actual body is gonna be almost exactly the same, but before we do the verify, we need to actually run our method. All right, so we're gonna update quality
25:40
and then verify the items after that. And again, that's gonna use the golden master testing. We get our approval error, we go run our verify. And so now it's comparing after one update. Notice it's getting like subdirectories and test names from RSpec, which is really nice. If you run this in Minitest, you have to provide a couple extra arguments, which is one of the reasons I'm using RSpec for this.
26:03
And it's showing the new output. Again, we don't care about what's in here. It doesn't matter if it's right. We just need to know we didn't change it. So I'm gonna approve that. And we'll go back and run our specs again. And now we've got a green bar again. Okay, so we could keep doing this. How far should we go?
26:21
How many of these tests do we need to write? That's one option. In this particular code, though,
26:41
it keeps decreasing the cell end day no matter how many times you run it. So yeah, it was a good idea. So his comment was that we run it until the output stops changing. So we run it until basically it gets everything all the way down. And that won't actually work in this case because cell end just keeps going down and down. It goes negative and keeps going negative. So we can't do that here. That would be a good thing to try, though.
27:01
I just don't wanna take the time in the workshop to do it right now until the values become invalid. Yep. Yeah, they don't actually ever become invalid here. So that will never stop.
27:20
So I wanna introduce you to a little tool you've probably heard of. It's called SimpleCov that does code coverage. And what it does is you run your tests and it tells you how many lines of your code have executed or which ones have been executed and which ones haven't. So you can run this. I actually have it hooked in so it runs this part of the test all the time. It produces a directory called coverage that has index.html you can look at.
27:42
But Ruby of mine has a nice integration for this. So I'm actually just gonna run this test with coverage. And it pops up a little coverage window here that I'll try to maximize. And all we really care about is our gilded rose file. So I'm gonna dive into that.
28:01
So it's saying 67% lines covered. And if we go back to the code, it has actually turned up the contrast on this. So I hope you can see it down the left side. You see these green bars all the way down the left? Are those visible at all out there? Okay, if you scroll down a little bit, there's also some red bars there.
28:21
So anything that's red is a line of code that has not been covered yet. Okay, so when I ran that test, all of these red lines of code have not yet been covered. It doesn't actually mark the ends and stuff like that. So this gives us a sense of how much of our code has been covered. So at this point we could say, probably our tests aren't quite good enough yet
28:42
to really feel comfortable refactoring. So we should probably keep going. And this is another place where Ruby comes in really nice because we can just kinda, these tests are very, very similar. So I'm gonna do them in a loop. So let's start with, let's go twice because that's what we're doing.
29:01
So I'm just gonna refactor the tests now. As long as the bar stays green, we're okay. Jillian did a talk yesterday. How many of you saw Jillian's talk on refactoring late yesterday afternoon? Anybody? She did a great job. And one of the points she made in that talk was that you should only be changing your code or your tests at the same time, never both because your tests are your safety net
29:22
for the code and vice versa. So we're gonna change our tests a little bit. We're not gonna touch the code while we do that. So we're gonna just iterate two times. And within that, we're gonna generate specify blocks here.
29:49
Again, these are not tests you necessarily wanna keep. But they're gonna work for now. So all I'm doing is I'm gonna repeat this loop twice and I is gonna start at zero and then be one.
30:03
And then after that many updates, we run the update quality method that many times. So zero or one times. And then we run verify afterwards. So this should exactly duplicate the test that we just had. Let's run them. And not quite. And the reason, if we look at it,
30:22
it's another approval error. Couldn't find an after one updates dot approve. So we had called our other one after one update because we actually used proper English in ours. We're gonna go with that for now. Because again, these are throwaway tests. They're just there to get us to where we need to be. So I'm gonna run my approvals verify again. And I could actually, if I wanted to, spec fixtures.
30:48
Just to be really safe. So I've got this after one update and after one updates. I could actually diff those two files.
31:00
Just to make sure they really haven't changed other than the name. And there's no difference. So we're pretty sure we can do this. So now I'm gonna run my verify again. Unfortunately, approvals doesn't actually walk back up
31:21
the directory tree to the root. So you have to kinda get there yourself. That might be my next pull request to approvals. All right, so I'm gonna go ahead and accept that. And we'll make sure we're green now. And we are, okay. So we already know that only doing one update only covers our code about 67%.
31:41
So we could try two and then three and then four. I'm gonna jump up a little bit. We'll go to five and try that. Just to move things along a little bit. Again, we've got approval errors. Let's go verify those. So that's four and three and two.
32:01
And green bar. And let's rerun with coverage again. Look at our code. So there's still some red bars in here. Just a few, but still a few. So let's try ten times. So this is really kinda mindless.
32:22
Just try to get enough coverage on the code to be comfortable with it. So we'll go ten, and we'll go approve everything. So there's five updates. And seven and six and eight and nine. Now we're green. And run with coverage.
32:45
So we're at 93% down here it says. If we look at our code, there's still this couple lines of code that are still not being covered. So I'm just gonna bump it up. I happen to know that 20 works, so we'll just go ahead and do that. And go approve all those.
33:11
Okay, and one more time with coverage. So people talk about code coverage and whether it's useful or not.
33:22
And whether you should shoot for 100% coverage or not. I'm not gonna get into that debate a lot. It can be a handy metric to watch trends, but it shouldn't be the end goal. It should be a tool that you use in service of a more important goal. But now we are 100% covered. So how confident do we feel refactoring this code now
33:42
with 100% code coverage? 50%? Anybody else? Okay, so if we're confident in our code, if I was to comment out, say, oh, I don't know this line and this line, that should break something, right?
34:06
We have 100% code coverage, but there's lines of code here that we can comment out and our tests don't fail. I don't know about you, but that scares me. What's really going on in this case, don't dive too deep into it,
34:21
is that there is an implicit else right here that is not shown in the code, and the else clause of this if statement is not actually ever getting hit. So code coverage is helpful, but it's not 100% reliable either. So you have to be careful with something like that.
34:41
Okay, but for now, I'm gonna call this good enough. So the problem why we're not covering all the lines of code is our tests are actually coupled to the predefined set of items in our initialize method. And those items in their current state do not exercise all of the possible cases in this code.
35:00
And so this is actually a dependency that we ultimately want to break, but in order to break that dependency, we needed to have some test coverage and we need to change the code. So we don't have a way to inject new items into this test very easily. I guess we could add some to the end of the array, but we're not allowed to touch that array because of the troll. So we have to work within the constraints we have, and so we're gonna have to start changing
35:23
production code without being 100% confident that we're not gonna break anything. But we're pretty sure, I mean, these are pretty good tests. They're just not perfect, and I wanted to make sure that we pointed that out. Okay, so now it's time to start cleaning up the mess. We have at least enough of a safety net that we're comfortable moving forward.
35:41
So let's talk about how we're gonna do that a little bit, and I need to switch back to Keynote for a minute for that. Okay, so we did that. All right, so I talked a little bit earlier about rewriting. We do not wanna rewrite.
36:00
Almost never is a rewrite a good idea. If you remember the browser wars back in the day, there was this thing called Netscape. They stopped the world to rewrite their browser and got completely destroyed by IE and didn't really come back until it was in the form of Firefox, and that took years. They did a stop the world rewrite on their code. It was just too crafty. We can't work in here anymore, and they died basically.
36:23
Joel Spolsky writes about rewrites and how they're a horrible idea. Sometimes there's nothing else you can do, but I wanna try to teach some tools that if you can't rewrite, what do you do instead? Sandy Metz actually did a talk here last year using the same Kata, and she actually
36:41
didn't refactor the code. She did an incremental rewrite on it. It was very piece at a time, piece at a time, test driven, it was great. It was an awesome talk. You need to watch it if you haven't seen it, but it was not actually a strict refactoring, and so I wanna kinda go a different direction and let's do this straight up refactoring and see where we can get to. We may not get through it all today given the time we have available, but we'll see how far we can get with it,
37:02
and we have to also remember we're probably not gonna get the code all the way cleaned up, but let's follow the Boy Scout rule and at least leave the code cleaner than we found it. We could go hack in this new feature right now, and the code would be worse, and that's not, if you have to keep coming back to this code, make it a little bit better every time, and over time, it will get better.
37:23
The parts of your application you touch the most will become the cleanest parts of the application if you follow this rule, and that's huge. I mean, you can't stop the world for three weeks to clean code up usually, so make the code a little bit better as you go. We're gonna take baby steps, I talked about that already.
37:42
We're not gonna boil the ocean. We have to remember, we're trying to implement a feature here. We're paying down technical debt. We're taking longer than it would take to just write the feature in a clean system because of the mess it's in, but we have a feature to do, and so we can't lose sight of that. Now, because of the speed we'll go in the workshop, it'll seem like we're losing sight of it a little bit,
38:00
but when you're working on your own code, keep in mind the end goal and the Boy Scout rule, so make it somewhat better. It might not be perfect yet. Make it better, okay? So Kent Beck has a great suggestion for how to make changes to code. You make the change easy, which might be hard to do, and then you make the easy change, and that's kind of the approach we're gonna take,
38:21
and then when I was first researching, I actually did a talk on this Kata at Go! Go! Ruko last year, and I found this quote while I was preparing for it, and I had to include it. So when you start a new programming job, you have to walk right up to the biggest function in the yard and refactor it in front of everyone. So that's kind of like prison advice, right? But that's what we're gonna do. Update quality is the biggest function in the yard,
38:40
so let's go refactor it, okay? So we're gonna keep going with the mob programming a little bit. Again, if you wanna just kind of run off on the side on your own and play with this, feel free. Before we start refactoring, I'm actually gonna commit the tests in the current state so that you can work from those. So let's go ahead and do that. Let's keep resetting.
39:03
So I'm actually gonna create a new branch first called RailsConf 2015, okay? And I need to add some files.
39:28
I always forget the shortcut for that. And the spec fixtures.
39:40
Okay, those are all added. I'll go ahead and commit, and push assuming Wi-Fi is working. All right, so you should be able to pull down that RailsConf 2015 branch if you wanna kind of work on this on your own.
40:01
For now, we'll just keep going with the mob programming a little bit, and then we'll see if you guys wanna kind of do some pair of small group work, or keep going with the mobbing. Okay, so we have our tests. We really don't need to look at them again, probably, for now. So what we wanna do, like I said, we wanna stay focused. So let's, there's some messes up here,
40:22
but those aren't kind of the core of the problem. Let's focus on updateQuality, since that's where we know we need to make the change. Now, if I look at this code, I don't really wanna, this is the least I'm ever gonna know about this code, and this is not when I wanna start making the huge changes, because I don't understand it yet, and I don't wanna study it for three days to figure it out. So what I wanna do is do really simple,
40:41
brain-dead seeming changes, just to try to get a sense of the code. It's like a potter working with the clay, and just kind of feeling the clay a little bit. It's kind of what we're doing with our medium here, which is the code. And so we're gonna try to make really simple changes, and we want some sense of whether we're making the world a better place. So what I'm gonna do is flip over to the terminal for a second.
41:01
And I have a rake task called Flog, and it runs a tool by Ryan Davis, who's speaking right after this workshop, actually, on Minitest. He wrote Flog, and what it does is it basically gives you a sense of how ugly your code is. It runs something called a cyclomatic complexity analysis on it, which is a big phrase,
41:20
but it basically tells you how gross your code is. Flog is actually a tool, I believe it's used by Code Climate, so part of your Code Climate score is what Flog says. So we're gonna run this, and just see where we're starting. So the overall code base has a score of 180.5, and the update quality method, as we surmise, is by far the worst. It's 155.1.
41:41
So that's our starting point, and we're gonna kinda see how we do against that as we go along. So let's flip back to the code. What's something really, really simple we can do to this code that would make it just a little bit better, a little easier to understand? Yep.
42:08
Okay, so actually make this an idiomatic Ruby each loop, instead of this thing that was ported from C sharp. And so, but first you said do something with items I.
42:28
Okay, so I think I understand what you're saying. So I'll do a couple little mechanical things. Yeah, no, I understood the language, I just wanna make sure I'm going the direction you're saying. So what he's saying is we need to do something with items I, like maybe extract it
42:40
to a variable called item, that would then become the iterator variable. So I'm gonna do this baby steps like I talked about. So the first thing I'm gonna do, now RubyMine has some refactorings, which is why I like using it. So we can actually go refactor extract variable. I usually use keyboard shortcuts for this, but I wanted to kinda show you where it is. And it says, do you wanna replace
43:01
all 34 occurrences of this expression? Yes, I think I really do, thank you. So I'm gonna call it item, boom. We're gonna run our tests, and I'm gonna flip over and run Flog again. That was a pretty big improvement. That was awesome. So that got our score down quite a bit.
43:21
We've now left the code better. If we had to hack in our new feature, at least the code's a little bit better. But we don't have to hack in our new feature yet because we still have 45 minutes to go so we can keep going. All right, so now you want us to turn this into an each loop instead, right? Okay, so does everybody feel pretty comfortable without refactoring at this point?
43:42
Okay, should be reasonably safe, right? So the idea is we replace the for loop. So actually, before I do that, is that for loop really equivalent to items.each? How hard do you have to study it to figure that out? Kinda have to stare at it a little bit, right?
44:00
It's like, what's the size minus one thing? Oh yeah, it's zero-based indexing. It's probably equivalent to an each, but you have to stare at it. And so one of the things I would like to argue is that idiomatic code, code written as native speak, is much easier to understand for new readers because you don't have to stare at it and figure out what it's really doing. You just know, oh, I know that idiom, great.
44:23
So any place where you can make the code more idiomatic really improves the code. One of the things we did with that variable abstraction, we got rid of some duplication, but we also got rid of some noise in the code, and that also makes it easier to read. So what's that?
44:45
We're writing Ruby, do we care about speed? No, I'm kidding, joking. But yeah, it is actually speed boost as well. So items.each do, and we'll call that item, and so that we should be able to get rid of those two lines and change nothing else, and broke something.
45:03
Right, instance variable, thank you. Test caught me, that's what they're there for. Okay, so that's a green bar. That's another good refactoring. When I was teaching this to the community college class last week, they got completely addicted to running Flog all the time, that's hilarious.
45:21
So that actually made it worse. That surprises me a little bit. I didn't expect that. But it's making it worse, and we're gonna make it better in a minute. All right, what's something else we can do to this code just to try to get some noise out of there, or make it a little more understandable so that we can add our feature?
45:46
Extract the quality incrementer? Okay, so like extract a method for it? Okay, so what do you wanna extract here? Just this one line? Okay, we can do that. I wanna point out this pattern, and that pattern.
46:08
So that three-line fragment is repeated three times.