Re: Large Java classes wanted
J Arrizza <[email protected]> Mon, 22 Feb 2010 21:26:50 -0800
| Newsgroups | gmane.comp.programming.refactoring |
|---|---|
| Message-ID | <[email protected]> |
On Mon, Feb 22, 2010 at 5:23 PM, Heinrich Breedt <[email protected]> wrote: > http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 > The Feathers book is very good. Here's some additional strategies to consider. You will use two basic refactorings: Extract Method and Extract Class. You will use Extract Method most of the time, but that's just a stepping stone to the real big-bang-for-buck workhorse, Extract Class. The basic thing to realize is that a very large class is a set of smaller classes intertwined together. And that a class is a cohesive set of methods that associate strongly with each other and associate (very) strongly with a small set of variables. 1) The primary strategy to use is: a) isolate one member variable of the large class and extract it into a private class as a public variable old: private int mVar; new: private class SomeThingClass { public int mVar; } b) instantiate the new class in the large class SomeThingClass mNewClass = new SomeThingClass(); c) search for all lines in the class that use or modify that member variable and replace them with calls into the new private class: old: mVar++; new: mNewClass.mVar++; d) start extracting lines of code that use or modify that member variable into the new private class: old : mVar++; new: mNewClass.Bump(); and in SomeThingClass: void Bump() { mVar++; } e) after a few extractions, you will probably need to rename the class since it's purpose should become clearer as you extract more lines. f) check for member variables in the Large Class that are not standalone but are attributes of other member variables (for an analogy think "Third Normal Form"). Eg. string[] mAppleTypes; // holds data int mNumberOfAppleTypes; // holds information about mAppleTypes, i.e. it's an attribute These two variables should be extracted out together. 2) You probably don't have unit tests in place. So add some -- any -- testing, whatever you can manage, it doesn't have to be pretty. They must be automated but they don't have to test at the unit level. 3) No unit tests mean high risk. So minimize the risk by looking for ways to simplify the large class by doing very small, very simple extractions. Typically look for 2 - 5 lines of code you can extract out into a void-void method: ... stmt1; stmt2; stmt3; ... replaced by: ... SomeGoodFunctionName(); ... private void SomeGoodFunctionName() { stmt1; stmt2; stmt3; } The key is to find statements that are tightly coupled for some reason, e.g. they all act on a single member variable, they perform a very simple, but atomic (can't be reduced any further) kind of action, etc. By the way, most programmers consciously or sub-consciously use the "white space" around statements to set them off (and some even put a comment or two above them). This is a coarse way to look for void-void sets of statements, but believe it or not, does work well since you are piggybacking on top of previous developer's understanding of "cohesion". Once you have a whole set of these smaller functions, look for commonality amongst them. If two or more functions all act on a single member variable or they all modify/use a small number of member variables, extract them out into their own class. 4) Look for methods in the Large Class that repeat the same formal variables in their signatures. Extract these out into their own class. The methods of the new class are the old methods without the formal variables, the member variables are the old formal variables. E.g. old: void fnA(int val1, int val2, int val3); void fnB(int val1, int val2, int val3); new: private class SomeNewClass { private int val1; private int val2; private int val3; public SomeNewClass(int v1, int v2, int v3) { val1 = v1; val2 = v2; val3 = v3; } public void fnA() { ... } public void fnB() { ... } } Why? because formal variables are tightly coupled to the method that uses them. If several methods use the same formal variables, then those methods are most likely tightly coupled to each other (i.e. they form a class). 5) Repeat 1 - 4 as often as you can. My experience has been that the original Large Class pretty much disappears leaving only a vestige of itself (just like the Chesire Cat). 6) As the primary purpose of the extracted classes becomes clearer, start adding Unit Tests for them that reinforces the intent of the new class. That actually helps clarify their purpose much further and may lead to a splitting of that class again (this is a good thing!). 7) Checkpoint the source files often. You will probably have to backtrack when you go down the wrong rabbit hole and so you'll need a quick way of going back to a known good point. Not glamorous work, but does pay off. I had one chunk of code that started off as 60,000 lines of VB and C++ code, boiled it down to 12,000 over several months. Lot of duplicate code disappeared, a lot of dead code was deleted, and my personal favorite, useless code was deleted. Useless code is code that is executed, but never actually changes the state of the system (yes it can change variables but those variables aren't significant to the system's purpose, so they can be deleted along with all the code that acted on them). John ------------------------------------ Yahoo! Groups Links <*> To visit your group on the web, go to: http://groups.yahoo.com/group/refactoring/ <*> Your email settings: Individual Email | Traditional <*> To change settings online go to: http://groups.yahoo.com/group/refactoring/join (Yahoo! ID required) <*> To change settings via email: [email protected] [email protected] <*> To unsubscribe from this group, send an email to: [email protected] <*> Your use of Yahoo! Groups is subject to: http://docs.yahoo.com/info/terms/