244 lt's AII Object Oriented 、 /IY PROJECT ls NOT OBJECT ORIENTED. How DO I MAKE SAFE CHANGES? Does that change the behavior of the system? Not really. That change was Just a mechanical process, and it kept the meaning and behavior Of the program exactly the same. The 01d C system was, ln reality, just one big 0bject. When we start using E 〃々 G んわ記 Re ん〃じぉ ( 339 丿 we're making new objects, and subdividing the system in ways which make it easier tO work with. When procedural languages have object-oriented extensions, they allow us tO move in this direction. ThiS isn't deep obJect-onentation; it's Just using objects enough t0 break up the program for testing. What can we do besides extracting dependencies when our language sup- ports OO ? For one thing, we can lncrementally move it toward a better ObJect design. ln general, this means that you have tO group related functions in classes and extract plenty 0f methods so that you can break apart tangled responsibili- ties. For more advice on this, see Chapter 20 , T ん 5 Class な 0 Big 4 れ d ー DO れ 7 Wa 厩〃 Get A 〃ァ Bigger. procedural COde doesn't present us with as many optlons as object-orlented code does, but we can make headway in procedurallegacy code. The particular seams that a procedural language presents critically affect the ease Of the work. If the procedural language you are using has an object-oriented successor, I recommend moving toward it. 〇わルけ seams ( 4 の are good for far more than getting tests in place. Link and preprocessmg seams are great for getting COde under test, but they really don't d0 much t0 improve design beyond that.
TEST-DRIVEN DEVELOPMENT (TDD) Write a FaiIing Test Case The next piece of code that we have to write is a method that calculates the sec- ond statistical moment about a point. Actually, it is just a variation of the first. Here iS a test that moves us toward writing that code. ln this case, the expected value is 0.5 rather than ー 0.5. We write a new test for a method that doesn't exist yet: secondMomentAbout. public void testSecondMoment() throws Exception { lnstrumentCalculator calculator = new InstrumentCa1culator() ; calculator. addEIement(). の ; calculator. addEIement(). の ; assertEquaIs(). 5 , calculator. secondMomentAbout(). の , TOLERANCE) ; Get lt to Compile TO get it tO compile, we have to add a definition for secondMomentAbout. We can use the same trick we used for the fi rstMomentAbout method, but it turns out that the code for the second moment is only a slight variation of the code for the first moment. This line in fi rstMoment: numerator 十 = element - point; has tO become this in the case Of the second moment: numerator + = Math. pow(element ー point, 2. の ; And there is a general pattern for this sort of thing. The nth statistic moment numerator 十 = Math. pow(element ー point, N) ; iS calculated using this expression: rename it SO that it is now called secondMomentAbout: TO make it compile, we can make a copy of the fi rstMomentAbout method and 、嶬 / e can generalize later if we find that we still want to. finish what we've started so far. Our only job right now is to make it compile. lt seems like we are getting lost in thought here. We should put this on ho 旧 and an N value, and we don't want to allow clients to supply an arbitrary value for N. method. We can do that, but it would burden the callers with the need to supply we can replace every use of fi rstMomentAbout(doub1e) with a call to that general write a general method that accepts an 、、 about" point and a value for N. Then At this point, we have a couple of choices. We can notice the generality and Math. pow(element ー point, 1. の . poi nt iS the same as The code in fi rstMomentAbout works because element 91 ()D D) Development Test-Driven
EXTRACT IMPLEMENTER «interface» ProductionNode 361 lmplementer Extract Node «interface» ModelNode + addExteriorNode(Node) + addInteriorNode(Node) + colorize() Figure 25.4 E はルゆル川群な催 0 〃 M0de1N0de. ProductionModelNode + addExteriorNode(N0de) + addInteriorNode(Node) + colorize() LinkageNode When you have a class embedded in a hierarchy like this, you really have to consider whether you are better off using E ズ〃 4 け r 卩 62 ノ and picking different names for your interfaces. lt iS a far more direct refactoring.
84 lT TAKES FOREVER TO MAKE A CHANGE Now we have a package, Opportuni tyProcessi ng, that really has no dependen- C1es on the database implementation.Whatever tests we and place in the package should compile quickly, and the package itself doesn't have t0 recom- pile when we change COde in the database implementation classes. The Dependency lnversion Principle When your code depends on an interface, that dependency is usually very mmor and unobtrusive. Your COde have tO change inter- face changes, and interfaces typically change far less often than the code behind them. When you have an interface, you can edit classes that imple- ment that interface or add new classes that implement the interface, all with- out impacting COde that uses the interface. For this reason, it is better tO depend on interfaces or abstract classes than it is tO depend on concrete classes.When you depend on less volatile things you minimize the chance that particular changes will trigger recompilation. So far, we've done a few things t0 prevent AddOpportuni tYFormHandl er from being recompiled when we modify classes it depends upon. That does make builds faster, but it is 0 司 y half of the issue. We can also make builds faster for code that depends on Add0pportunityFormHandl e 「 . Let's 0k at the package design again, in Figure 7.5. Breaking Dependencies OpportunityProcessing + AddOpportunityFormHandIer + AddOpportunityFormHandIerTest - AddOpportunityXMLGenerator - AddOpportunityXMLGenerato 「 Test DatabaseGateway + ConsultantSchedulerDB + 0P8 血 n em DatabaseImplementation + Consu ItantSchedulerDBImpI + ConsuItantSchedulerDBImpITest + Opportunityltem lmpl + OpportunityltemImplTest Figure 7.5 P た ag ビ立川け 4
LEARNING FROM EFFECT ANALYSIS class Coordinate { protected : mutable double fi rst, second; ln C 十 + , when the keyword mutable is used in a declaration, it means that those variables can be modified in const methods. Admittedly, this use 0f mutable is particularly Odd, but when it comes tO figuring out what can and can't change in a program that we don't know well' we have t0 100k for effects regardless 0f how 0dd they might be. Taking const t0 mean const in C + + without really checking can be dangerous. The same hOlds true for Other language constructs Learning from Effect Analysis Know your language ・ that can be circumvented. Effect AnaIysis Learning from CppClass object after we created it. Here is an excerpt of that code: did a little exercise in which we tried tO figure out what would affect users Of a often contextualthings. ln the CppCl ass example at the beginning 0f chapter, we ming style, things such as 。、 Never use protected variables. ' ' lnstead, they are The "rules" for a code base aren't necessarily grand statements Of program- don't know what the 。、 rules" are, or the "rules" are littered with exceptions. base has a lot of rules like that, it is far easier t0 deal with. ln bad code, people base. Then say to yourself, "But, no, that would be stupid. " When your code ware could have an effect on another, a way that you ve never seen in the COde effects. The best way to find these rules is t0 think 0f a way that a piece 0f soft- stated or not, prevent you from having t0 be paranoid as you 0k for possible chas. ' ' Some "rules" embodied in the code base, whether they are explicitly "basic goodness" in your code base. ln the best code, there aren't many got- have t0 100k for certain things. When you feel that way, you've found some notice that as you get very familiar with a code base, you feel that you don't Try tO analyze effects in COde whenever you get a chance. Sometimes you will
PULL Up FEATURE ls this a good thing? From a design point of view, it is less than ideal. we've spread a set Of features across t 、 VO classes Jt1St tO make it easier tO test. The spread can be confusing if the relationship among the features ln each of the classes isn't very strong, and that is the case here. ・ / e have Scheduler, which is responsible for updating scheduling items, and Schedu1i ngservi ces, which is responsible for a variety 0f things, including getting the default times for items and calculating the dead time. A better factoring would be to have Schedu1er del- egate tO some validator Object that knows hOW tO talk tO the database, but if that step 100kS t00 risky to do immediately or there are other bad dependencies, pulling up features is a good first step. If you P 尾“れ℃ S / g 〃 4 ル尾 5 ( 372 ) and Lean 0 〃 the CO 川々〃げ ( 37 , it is far less risky. We can move toward delegation later When more tests are in place. Steps To do P ″〃 Up ん 4 尾 , follow these steps: 1. ldentify the methods that you want to pull up. 2. Create an abstract superclass for the class that contains the methods. 3. Copy the methods to the superclass and compile. 4. Copy each missing reference that the compiler alerts you about to the new superclass. Remember tO 尾 5 ビルビ g 〃 4 s ( 372 丿 as you do this, tO reduce the chance Of errors. 5. When both classes compile successfully, create a subclass for the abstract class and add whatever methods you need tO be able to set it up in your tests. You might be wondering why we make the superclass abstract. I like to make it abstract SO that the code is easier tO understand. lt is great to be able to look at the COde in an application and know that every concrete class is being used. If you search the COde and find concrete classes that are not being instantiated anyplace, they could appear t0 be 、 'dead code. '' 391 Pull Up Feature
TACKLING MONSTERS WITH AUTOMATED REFACTORING SUPPORT Here iS an example: cl ass Commodi tySeI ectionPaneI public void update() { if (commodities. size() > 0 commodities. GetSource(). equals("local")) { listbox. clear(); for (lterator it = commodities. iterator() ; it. hasNext(); ) { Commodi ty commodi ty = (Commodity)it. next() ; if (commodity. isTwiIight() ! commodity. match(broker)) listbox. add(commodity. getView()) ; ln this method, a 10t of things could be cleaned up. One of the odd things is that this sort of filtering work is happening on a panel class, something that should ideally just be responsible for display. UntangIing this code is bound to be difficult. If we want to start writing tests against the method as it stands now, we could write them against the list bOX state, but that wouldn't move us t00 far along toward making the design better. ・ With tool-based refactoring support, we can start to name high-level pieces of the method and break dependencies at the same time. This is what the code would 100k like after a series Of extractions. commodities. GetSource() . equals("local") ; return commodities. size() > 9 pri vate boolean commoditi esAreReadyForUpdate() 叩 dateCommodities() ; cl earDispI ay() ; i f (commoditiesAreReadyForUpdate()) { public void update() { class Comm0ditySeIectionPaneI 295 Support Refactoring with Automated TackIing Monsters
EXTRACT AND OVERRIDE GETTER E 工な 4 じー 4 れ d 〇レ e な卍 G ビなど is not a technique that I use very often. When there is Just a single method on an Object that iS problematic, it is far easrer to use Extract 〃 d 〇怩な / Ca 〃卩 48 丿 . But, Ex な 4 け 4 〃 d 〇阨なビ Getter is a bet- ter chOice when there are many problematic methods on the same object. If you can get rid 0f all 0f those problems by extracting a getter and overriding it, it is a Clear WIII. Steps To Ex な 4 は 4 れ d 〇怩な / G 召な , follow these steps: 1. ldentify the object you need a getter for. 2. Extract all of the logic needed to create the object into a getter. 3. Replace all uses of the object with calls to the getter, and initialize the reference that hOlds the Object tO null in all constrUctors. 4. Add the first-time logic to the getter so that the object is constructed and assigned tO the reference 、 vhenever the reference iS null. 5. Subclass the class and override the getter to provide an alternative object for testing. 355 Extract and Override Getter
Chapter 13 I Need to Make a Change, but I Don't Know What Tests tO Write 185 want to change with tests t0 provide some kind 0f safety net. We'll find bugs approach we can take when we need tO make changes is tO bOlSter the area we that we re preserving behavior when we make changes. For this reason, the best any tests for the changes we need tO make, SO there isn't any way tO really verify Where does this leave us with legacy code? ln legacy code, we might not have when you change behavior that you didn't expect t0. bugs, but usually not the first time that a test is run. You find bugs in later runs flow of development, tests that 斗“ / become tests that 々尾ル巳 You will find to fulfill or attempt to preserve behavior that is already there. ln the natural directly, at least. ln general, automated tests should specify a goalthat we'd like Automated tests are a very important tOOl, but not for bug finding—not concentrate effort on not putting bugs intO COde in the first place. helps your team start tO wrlte correct COde consistently. The way tO win IS tO it can actually be misdirected effort. lt is usually better tO dO something that No, finding bugs in legacy code usually isn't a problem. ln terms of strategy, weren't what they could've been. for its changes has ended far behind. The confidence levels of those teams do that. Nearly every team l've worked with that depended on manual testing and over again whenever you change the code. And, frankly, people just don't bugs very quickly. The downside is that you have t0 dO that manual work over you have some way 0f exercising legacy code manually, you can usually find bugs in legacy code often doesn't feel as efficient as Just trying out the code. If tO find bugs. Often these tests are manual tests. Writing automated tests tO find When people talk about testing, they are usually referring to tests that they use ー N eed to Make a Change
Chapter 11 I Need to Make a Change. What Methods Should I Test? 、、 need tO make some changes, and we need tO write c わ arac た 4 〃 0 〃 s な 卩 86 丿 to pin down the behavior that is already there. Where should we write them? The simplest answer is tO write tests for each method that we change. But is that enough? lt can be if the code is simple and easy to understand, but in legacy code, often all bets are 0 圧 A change in one place can affect behavior someplace else; unless we have a test in place, we might never know about it. When I need to make changes in particularly tangled legacy code, I often spend time trying tO figure out where I should write my tests. This involves thinking about the change I am going to make, seeing what it will affect, seeing what the affected things will affect, and so on. This type of reasoning is nothing new; people have been dOing it since the dawn Of the computer age. Programmers Sit down and reason about their programs for many reasons. The funny thing is, we don't talk about it much. 嶬 just assume that everyone knows hOW tO dO it and that dOing it is just part Of being a programmer. Unfortunately, that doesn't help us much when we are confronted with terribly tangled code that goes far beyond our ability to reason easily about it. We know that we should refactor it tO make it more understandable, but then there is that issue Of testing again. If we don't have tests, how dO we know that we are refactoring correctly? I wrote the techniques in this chapter to bridge the gap. Often we do have to Reasoning About Effects reason about programs ln non-trivial ways tO find the best places tO test. 151 in SOft 、 vare, there is some associated chain Of effects. For instance, if I change ln the industry, we don't talk about this often, but for every functional change ー Need to Make a Change