about - みる会図書館


検索対象: WORKING EFFECTIVELY WITH LEGACY CODE
152件見つかりました。

1. WORKING EFFECTIVELY WITH LEGACY CODE

Four Reasons tO Change Softwa re CHANGING SOFTWARE meeting with people in her department, and they decided t0 change the 10g0 placement and ask for a bit more functionality. FrOI れ a developer's point Of view, the change could be seen as a completely new feature. "lf they just stopped changing their minds, we'd be done by now. ' ' But in some organiza- tions the logo move is seen as Just a bug fix, regardless Of the fact that the team is going t0 have t0 d0 a 10t 0f fresh work. lt is tempting t0 say that all 0f this is just subjective. You see it as a bug fix, and I see it as a feature, and that's the end 0f it. Sadly, though, in many organi- zatlons, bug fixes and features have tO be tracked and accounted for separately because Of contracts or quality initiatives. At the people we can go back and forth endlessly about whether we are adding features or fixing bugs, but it is all just changing code and other artifacts. Unfortunately, this talk about bug- fixing and feature addition masks something that iS much 1 れ ore important tO us technically: behavioral change. There is a big difference between adding new behavior and changing 01d behavior. Behavior iS the most important thing about software. lt is what users depend on. Users like it when we add behavior (provided it is what they really wanted), but if we change or remove behavior they depend on (introduce bugs)' they st0P trusting us ・ ln the company 10g0 example, are we adding behavior? Yes. After the change, the system will display a go on the right side 0f the page. Are we get- ting rid 0f any behavior? Yes, there won't be a go on the left side. Let's 100k at a harder case. Suppose that a customer wants tO add a 10g0 tO the right side Of a page, but there wasn't one on the left side tO start with. Yes, we are adding behavior, but are we removmg any? Was anything rendered in the place where the logo is about t0 be rendered? Are we changing behavior, adding it, or both? We can draw a distinction that is more useful tO us as programmers. If we have to modify code (and HTML kind 0f counts as code), we could be changing behavior. If we are 0 司 y adding code and calling it, we are often adding behav- ior. Let'S 100k at another example. Here is a methOd on a Java class: publ i c cl ass CDP1ayer public void addTrackListing(Track track) { The class has a method that lets us add track listings. Let's add another method that lets us replace track listings.

2. WORKING EFFECTIVELY WITH LEGACY CODE

CHARACTERIZING CLASSES make in the code and try tO figure out whether the tests that we have will sense any problems that we can cause. If they won't, we add more tests until we feel confidence that they will. If we can't feel that confidence, lt's safer to consider changing the software in a different way. Maybe we can d0 a piece 0f what we Ⅵ℃ re considering first. The Method Use Rule Before you use a method in a legacy system, check tO see if there are tests for it. If there aren't, write them. When you dO this consistently, you use tests as a medium of communication. People can ー 00k at them and get a sense Of what they can and cannot 、一 expect from the method. The act Of making a class testable in itself tends tO increase code quality. People can find out what works and how; they can change it, correct bugs, and move forward. Characterizing Classes ・ / e have a class, and we want to figure out what to test. How do we do it? The first thing t0 d0 is to try to figure out what the class does at a high level. 嶬み e can wrlte tests for the simplest thing that we can imagine it dOing and then let our curioslty guide us from there. Here are some heuristics that can help: 1. Look for tangled pieces 0 日 ogic. If you don't understand an area of code, consider introducing a se 〃 5 ⅲ g レ ar わん ( 307 ノ tO characterize it. Use sensmg variables tO make sure you execute particular areas Of the COde. 2. AS you discover the responsibilities Of a class or method, stop tO make a list Of the things that can go wrong. See if you can formulate tests that trigger them. 3. Think about the inputs you are supplying under test. What happens at extreme values? 4. Should any conditions be true at all times during the lifetime of the class? Often these are called invariants. Attempt tO write tests tO verify them. Often you might have tO refactor to discover these conditions. If you do, the refactorings often lead to new insight about how the code should be. The tests that we write to characterize code are very lmportant. They are the documentatlon Of the system's actual behavior. Like any documentation that you write, you have tO think about what will be important tO the reader. Put yourself in the reader's shoes. What would you like tO know about the class you are working with if you'd never seen it? ln what order would you like the information? When you use the xUnit frameworks, tests are just methods in a 61e. 189 Characteriz- ing CIasses

3. WORKING EFFECTIVELY WITH LEGACY CODE

26 SENSING AND SEPARATION Fake Objects Support ReaI Tests Sometimes when people see the use 0f fake objects, they say, 。、 That's not really test- ing. '' After all, this test doesn't show us what really gets displayed on the real screen. Suppose that some part 0f the cash register display software isn't working properly; this test would never shOW it. 嶬でⅡ , that's true, but that doesn't mean that this isn't a real test. Even if we could devise a test that really showed us exactly which pixels were set on a real cash register display, does that mean that the software would work with all hardware? NO, it doesn't—but that doesn't mean that that isn't 竄 test, either. When we write tests, we have tO divide and conquer.. This test tells us hOW Sal e objects affect displays, that's all. But that isn't trivial. Ⅱ we discover a bug, running this test might help us see that the problem isn't ⅲ Sal e. Ⅱ we can use information like that tO help us localize errors, we can save an incredible amount Of time. When we write tests for individual units, we end up with small, well-understood pieces. ThiS can make it easler tO reason about our COde. Faking Collaborators The Two Sides of a Fake Object Fake objects can be confusing when you first see them. One of the oddest things about them is that they have two "sides," in a way. Let's take a 100k at the Fake- Di splay class again, in Figure 3.4. The showLi ne method is needed on FakeDisp1ay because FakeDi splay implements Disp1ay. lt is the 0 司 y method on Di spl ay and the 0 司 y one that Sa1 e will see. The other method, getLastLine, is for the use 0f the test. That is why we declare di s- play as a FakeDisp1ay, not a Display: FakeDisplay - IastLine : String The test cares about this + getLastLine() : String + showLine(Iine : String) The SaIe object on ツ sees this 石も , 0 s 卍お 4 たビ 0 を“た Figure 3.4

4. WORKING EFFECTIVELY WITH LEGACY CODE

152 Reasoning About Effects I NEED TO MAKE A CHANGE. WHAT METHODS SHOULD I TEST? the 3 to 4 in the following C# code, it changes the result of the method when it is called. lt could 引 so change the results of methods that call that method, and so on, all the way back t0 some system boundary. Despite this, many parts 0f the code won't have different behavior. They won't produce different results IDE Support for Effect Analysis return result; result + = load. getP0intWeight() ☆ SCALE-FACTOR; foreach(Load 1 oad i n 1 oads) { i nt resul t = starti ngLoad + (LOAD-FACTOR ☆ resi dual ☆ SCALE-FACTOR) ; const i nt SCALE-FACTOR = 3 ; int getBa1anceP0int() { because they don't call getBal anceP0i nt() directly or indirectly. me a list of all of the variables and methods that could be impacted when I change the would be able to highlight a piece of code and hit a hotkey. Then the IDE would give Sometimes I wish that I had an IDE that would help me see effects in legacy code. I selected code. public String getName() { return declarations. size() ; public int getDec1arationCount() { this. declarations = declarations; thiS. name = name; public CppC1ass(String name, List declarations) { private List declarations; private String name; public class CppC1ass { methods. after a CppCl ass object is created that would affect results returned by any 0f its Let's try a little exercise. Make a list 0f all 0f the things that can be changed doesn't matter when we reason about effects. C + + code. lt sounds pretty domain intensive, doesn't it? But domain knowledge example. Here is a Java class that is part Of an application that manipulates The best way tO get a sense Of what effect reasoning is like is tO lOOk at an when we've gotten it right. reason about effects without tOOls. lt is a very learnable but it is hard tO know Perhaps someday someone will develop a toollike this•ln the meantimg we have t0

5. WORKING EFFECTIVELY WITH LEGACY CODE

REASONING ABOUT EFFECTS return name ; public Dec1aration getDec1aration(int index) { return ((DecIaration)decIarations. get(index)) ; public String getInterface(String interfaceName, int [ ] indices) { String result = "class 十 i nterfaceName 十 ' {\npublic:\n" ・ for (int n = 0 ; n く indices. length; n + + ) { Declaration virtualFunction = (Dec1aration) (declarations. get(indices[n])) ; result + = "\t" + virtualFunction. asAbstract() + "\n" ・ return result; Your list should 旧 ok something like this: 1. Someone could add additional elements to the declarations list after passing it tO the constructor. Because the list iS held by reference, changes made tO it can alter the results Of getlnterface, getDeclaration , and getDeclarationCount. 2. Someone can alter one of the objects held in the declarations list or replace one Of its elements, affecting the same methods. Some people 100k at the getName method and suspect that it could return a different value if anyone changes the name string, but in Java, Stri ng objects are immutable. You can't change their value after they are created. After a CppC1 ass object is created, getName always returns the same string value. We can make a sketch that shows that changes in declarations have an effect on getDec1 arationCount() (see Figure 11.1 ). 153 Reasoning About Effects Figure 11.1 declarations getDeclarationCount declarations ゆ ac な getDec1arationCount .

6. WORKING EFFECTIVELY WITH LEGACY CODE

Chapter 4 The Seam Model One Of the things that nearly everyone notices when they try tO write tests for existing COde is just hOW poorly suited COde is tO testing. lt isn't Just particular programs or languages. ln general, programming languages Just don't seem t0 support testing very well. lt seems that the only ways t0 end up with an easily testable program are tO write tests as you develop it or spend a bit Of time trying to "design for testability. ' ' There is a lot of hope for the former approach, but if much 0f the code in the field is evidence, the latter hasn't been very successful. One thing that l've noticed is that, in trying to get code under test, l've started tO think about code in a rather different way. I could just consider this some private quirk, but l've found that this different way 0f looking at code helps me when I work in new and unfamiliar programming languages. Because I won't be able t0 cover every programming language in this book, l've decided t0 outline this view here in the hope that it helps you as well as it helps me. A Huge Sheet of Text When I first started programming, I waslucky that I started late enough to have a machine Of my own and a compiler tO run on that machine; many Of my friends starting programming in the punch-card days. When I decided t0 study programmrng in sch001, I started working on a terminal in a lab. ・ We could compile our code remotely on a DEC VAX machine. There was a little account- lng system in place. Each compile COSt us money out Of our account, and we had a fixed amount Of machine tlme each term. At that point in my life, a program was Just a listing. Every couple of hours, l'd walk from the lab to the printer room, get a printout of my program and scrutinize it, trying tO figure out what was right or wrong. I didn't know enough tO care much about modularity.We had tO write modular COde tO shOW that we could do it, but at that point I really cared more about whether the code was 29 The Seam Model

7. WORKING EFFECTIVELY WITH LEGACY CODE

190 Ta rgeted Testing I NEED TO MAKE A CHANGE You can put them in an order that makes it easier for people tO learn about the COde they exercise. Start with some easy cases that shOW the main intent Of the class, and then m. ove intO cases that highlight its idiosyncrasies. Make sure you document the important things that you discover as tests. When you get tO making your changes, often you'll find that the tests you've written are very appropriate for the work you are about t0 d0. Whether we think about it consciously or not, the change that we set out tO make Often guides our curiosity. is a bug and how best t0 deal with it. test COde as suspicious and then escalate it. Find out as quickly as you can whether it error, it should be fixed. If you suspect that some behavior IS wrong, mark it in the My bias is toward fixing bugs as soon as they are found. When behavior is clearly in out causing ripple effects. you see it as a bug. Often it takes a bit 0f analysis t0 figure out how to fix a bug with- tO examine the possibility that someone is depending on that behavior, even though answer is simple: You should fix the bug. If the system has been deployed, you need ー The answer depends on the situation. If the system has never been deployed, the を What should you do when you find a bug? AII legacy code has bugs, usually in direct proportion t0 how little it is understood. When you characterize legacy code, you will find bugs throughout the entire process. When You Find Bugs を cost + = 1.2 ☆ priceForGa110ns(ga110ns) ; else COSt 十 = corpBase ; if (gallons く Lease. CORP-MIN) if (lease. isMonth1y()) { public void addReading(int gallons, Date readingDate){ private ZonedHawthorneLease lease; private double corpBase = 12.0 ; private long cost public class FueIShare tanks: example, a method on a Java class that computes the value Of fuel in leased things that we want tO change and see if our tests really cover them. Here is an After we've written tests tO understand a sect10n Of COde, we have tO 100k at the Targeted Testing

8. WORKING EFFECTIVELY WITH LEGACY CODE

PREFACE base. ln the following chapters, I describe techniques that you can use t0 under- stand COde, get it under test, refactor it, and add features. One thing that you will notice as you read this b00k is that it is not a book about pretty code. The examples that I use in the book are fabricated because I work under nondisclosure agreements with clients. But in many Of the exam- ples, l've tried t0 preserve the spirit 0f code that l've seen in the field. I won't say that the examples are always representative. There certainly are oases Of great COde out there, but, frankly, there are alSO P1eces Of COde that are far worse than anything I can use as an example in this bOOk. Aside from client confidentiality, I simply couldn't put code like that in this book without boring you tO tears and burying lmportant points a morass Of detail. As a result, many of the examples are relatively brief. If you 0k at one of them and think "NO, he doesn't understand—my methods are much larger than that and much worse," please 100k at the advice that I am giving at face value and see if it applies, even if the example seems simpler. The techniques here have been tested on substantially large preces of code. lt IS Just a limitation Of the bOOk format that makes examples smaller. ln particu- lar, when you see ellipses (... ) in a code fragment like this, you can read them as insert 500 lines of ugly code here" m-pDispatcher->register(Iistener) ; m_nMargi ns 十十 ; If this book is not about pretty code, it is even less about pretty design. Good design should be a goal for all 0f us, but in legacy code, it is something that we arrive at in discrete steps. ln some Of the chapters, I describe ways Of adding new code tO existing code bases and show hOW tO add it with good design prln- ciples in mind. You can start t0 grow areas 0f very good high-quality code in legacy code bases, but don't be surprised if some 0f the steps you take t0 make changes involve making some code slightly uglier. This work is like surgery. We have tO make inclslons, and we have tO move through the guts and suspend some aesthetic Judgment. Could this patient's maJOr organs and viscera be bet- ter than they are? Yes. SO dO we just forget about his immediate problem, sew him up agaln, and tell him t0 eat right and train for a marathon?We could, but what we really need tO dO is take the patient as he is, fix what's wrong, and move him tO a healthier state. He might never become an Olympic athlete, but we can't let "best" be the enemy Of "better. ' ' COde bases can become healthier and easier tO work in. When a patient feels a little better, Often that is the time when you can help him make commitments t0 a healthier life style. That is what we are shooting for with legacy code. We are trying tO get to the point at XVII

9. WORKING EFFECTIVELY WITH LEGACY CODE

242 lt's AII Object Oriented 、 IY PROJECT ls NOT OBJECT ORIENTED. How Do I MAKE SAFE CHANGES? class Scanner publ i c: i nt scan-packets(struct rnode-packet *packet , int fla の ; Now we can apply ~ 4 川邵た Co 〃川け 0 ( 3 79 ) and change the class so that it uses a ResultN0tifier that we supply: class Scanner private: Resu1tNotifier& notifier; public: Scanner() ; Scanner(Resu1tNotifier& notifier) ; i nt scan-packets(struct rnode-packet *packet, i nt fl a の ; 〃 in the source file Scanner: :Scanner() : n0tifier(g10ba1ResuItNotifier) Scanner: :Scanner(ResuItN0tifier& notifier) : notifier(notifier) When we make this change, we can find the places where scan_packets is being used, create an lnstance Of Scanner, and call it fromthe instance. These changes are pretty safe and pretty mechanical. They aren't great exam- ples of object-oriented design, but they are good enough to use as a wedge to break dependencies and allOW us tO test as we move forward. lt's AII Object Oriented Some procedural programmers like tO beat up on Object orlentation; they con- sider it unnecessary or think that its complexity doesn't buy anything. But when you really think about it, you begin t0 realize that all procedural programs are Object oriented; it's Just a shame that many contain only one object. To see this, imagme a program with about 100 functions. Here are their declarations:

10. WORKING EFFECTIVELY WITH LEGACY CODE

GENERAL TEST HARNESSES General Test Harnesses The xUnit frameworks I described in the preceding section were designed to be used for unit testing. They can be used t0 test several classes at a time, but that sort 0f work is more properly the domain of FIT and Fitnesse. Framework for lntegrated Tests (FIT) FIT is a concise and elegant testing framework that was developed by Ward Cunningham. The idea behind FIT is simple and powerful. If you can write doc- uments about your system and embed tables within them that describe inputs and outputs for your system, and if those documents can be saved as HTML the FIT framework can run them as tests. FIT accepts HTML, runs tests defined in HTML tables ⅲ it, and produces HTML output. The output 100kS the same as the input, and all text and tables are preserved. However, the cells in the tables are colored green to indicate val- ues that made a test pass and red tO indicate values that caused a test to fail. You alSO can use options tO have test summary information placed in the result- ing HTML. The only thing you have to do to make this work is to customize some table- handling COde SO that it knows how to run chunks of your code and retrieve results from them. Generally, this is rather easy because the framework pro- vides code t0 support a number of different table types. One of the very powerful things about FIT is its capability to foster commu- nication between people whO write software and people wh0 need t0 specify what it should do. The people who specify can write documents and embed actual tests within them. The tests will run, but they won't pass. Later develop- ers can add in the features, and the tests will pass. BOth users and developers can have a common and up-to-date view Of the capabilities of the system. There is far 1 れ ore tO FIT than I can describe here. There is 1 れ ore information about FIT at http://fit.c2.com/ Fitnesse Fitnesse is essentially FIT hosted in a wiki. Most of it was developed by Robert Martin and Micah Martin. I worked on a little bit of it, but I dropped out to concentrate on this bOOk. l'm lOOking forward tO getting back to work on it soon. 53 General Test Harnesses