240 likes | 345 Views
Clean Functions in a Flash Index Cards Version 0.2. Contents. Decomposing Message Composed Method Intention Revealing Method Explaining Message Method Comment Comments Long Methods - The Geography Metaphor Long Methods - The Bedroom Metaphor Code Smell: ‘Long Method’
E N D
Contents • Decomposing Message • Composed Method • Intention Revealing Method • Explaining Message • Method Comment • Comments • Long Methods - The Geography Metaphor • Long Methods - The Bedroom Metaphor • Code Smell: ‘Long Method’ • Refactorings for Writing Composed Methods • Simplifying Conditionals: Decompose Conditional • Simplifying Conditionals: Replace Nested Conditional with Guard Clause • Simplifying Conditionals: Encapsulate Conditionals • Simplifying Conditionals: Avoid Negative Conditionals • Functions With Sections Do More Than One Thing • Functions That Do One Thing Cannot Be Reasonably Divided Into Sections • Ensuring Functions Do One Thing • Short Methods - No Guarantees • Short Methods - A Means to an End • How Long Should a Method be? • Effects of Extract Till You Drop • Object Oriented Decomposition Versus Functional Decomposition
Decomposing Message 1 • How do you invoke parts of a computation? • Send several messages to ‘this’ (old-fashioned functional decomposition) • Use Composed Method to break the method into pieces • Make each method an Intention Revealing Method • Use Explaining Message to communicate intent separate from implementation
Composed Method 2 How do you divide a program into methods? • Divide your program into methods that perform one identifiable task • Keep all of the operations in a method at the same level of abstraction • This will naturally result in programs with many small methods, each a few lines long
Intention Revealing Method 3 • What do you name a method? • Name it after what it does (not how it does it)
Explaining Message 4 How do you communicate your intent when the implementation is simple? • Send a message to 'this'. • Name the message so that it communicates what is to be done rather than how it is to be done. • Code a simple method for the message. public booleanisEmpty(){ size == 0; } private void addElement(Element element){ elements[size++] = element; } public void highlight(Rectangle area){ reverse(area); } Consider introducing an explaining message when you are tempted to comment a single line of code worse: flags |= LOADED_BIT; // Set the loaded bit better: private void setLoadedFlag() { flags |= LOADED_BIT; }
Method Comment 5 How do you comment methods? • Communicate important information that is not obvious from the code in a comment at the beginning of the method • I expect you to be skeptical about this pattern • Experiment: • Go through your methods and delete only those comments that duplicate exactly what the code says • If you can’t delete a comment, see if you can refactor the codeto communicate the same thing, especially using Composed Method and Intention Revealing Method • I will be willing to bet that when you are done you will have almost no comments left.
Comments 6 • Every comment you write is a failure to express yourself well in code • Comments are often used as a deodorant for the rotten whiffs of bad code • Strive to eliminate comments in your code • Whenever you feel the need to write a comment, first try to refactor the code so that any comment becomes superfluous • Apply the Method Comment pattern
Long Methods The Geography Metaphor 7 • We humans are very good at navigating complex terrains by recognising landmarks • Take a long function and turn it on its side, and it looks like a landscape. How do we know our way around inside a large function? We memorised the landscape and so we recognise the landmarks. • If you drop someone new into a landscape of code, they’ll recognise nothing. They’ll be completely bewildered. They won’t know where to go, or even how to begin, they’ll just have to wander around. • Don’t listen to your reptilian hindbrain. Don’t favour long methods just because they are comfortable in that you can treat them as landscapes • Remember that lots of little well named functions will save you and everybody lots of time because they are going to act as signposts along the way, helping everybody navigate through your code
Long Methods The Bedroom Metaphor 8 • Long functions are the way a 12 year old files things away • You feel comfortable with everything scattered through a whole lot of deep indents • This is not a good strategy for working and living in a team • It is hard to be a team when you are surrounded by a mess all the time • Later on as you mature and start working in a team you realize that the best place to put code is in nicely named places where it can be found later and understood
Code Smell: ‘Long Method’ 9 • Number two in the stink parade (after Duplicated Code) is Long Method. • Since the early days of programming people have realised that the longer a method is, the more difficult it is to understand. • The object programs that live best and longest are those with short methods. • You should be much more aggressive about decomposing methods • Whenever you feel the need to comment something, write a method instead
Refactorings for Writing Composed Methods 10 • A large part of refactoring (making code easier to understand and cheaper to modify) is composing methodsto package code properly. • Almost all the time the problems come from methods that are too long. • The key refactoring is Extract Method. • When applying Extract Method is problematic, use the following refactorings: • Inline Method • Inline Temp • Replace Temp with Query • Introduce Explaining Variable • Split Temporary Variable • Remove Assignments to Parameters • Replace Method with Method Object • Substitute Algorithm
Simplifying Conditionals: Decompose Conditional 11 Refactoring: Decompose Conditional • You have a complicated conditional (if-then-else) statement. • Extract methods from the condition, then part, and else parts. if(date.before (SUMMER_START) || date.after(SUMMER_END)) charge = quantity * _winterRate + _winterServiceCharge; elsecharge = quantity * _summerRate; if(notSummer(date)) charge = winterCharge(quantity); elsecharge = summerCharge (quantity);
Simplifying Conditionals: Replace Nested Conditional with Guard Clause 12 Replace Nested Conditional with Guard Clause • A method has conditional behavior that does not make clear the normal path of execution. • Use guard clauses for all the special cases. double getPayAmount() { double result; if (isDead) result = deadAmount(); else { if (isSeparated) result = separatedAmount(); else { if (isRetired) result = retiredAmount(); else result = normalPayAmount(); }; } return result; }; double getPayAmount() { if (isDead) return deadAmount(); if (isSeparated) return separatedAmount(); if (isRetired) return retiredAmount(); return normalPayAmount(); };
Simplifying Conditionals: Encapsulate Conditionals 13 Clean Code Heuristic G28: Encapsulate Conditionals Boolean logic is hard enough to understand without having to see it in the context of an if or while statement. Extract functions that explain the intent of the conditional. For example: if (shouldBeDeleted(timer)) is preferable to if (timer.hasExpired() && !timer.isRecurrent())
Simplifying Conditionals: Avoid Negative Conditionals 14 Clean Code Heuristic G29: Avoid Negative Conditionals Negatives are just a bit harder to understand than positives. So, when possible, conditionals should be expressed as positives. For example: if (buffer.shouldCompact()) is preferable to if (!buffer.shouldNotCompact())
Functions with Sections Do More Than One Thing 15 It is often tempting to create functions that have multiple sections that perform a series of operations. Functions of this kind do more than one thing, and should be converted into many smaller functions, each of which does one thing. This code does three things: • It loops over all the employees • checks to see whether each employee ought to be paid • and then pays the employee. public void pay() { for (Employee e : employees) { if (e.isPayday()) { Money pay = e.calculatePay(); e.deliverPay(pay); } } } It would be better written as public void pay() { for (Employee e : employees) payIfNecessary(e); } private void payIfNecessary(Employee e) { if (e.isPayday()) calculateAndDeliverPay(e); } private void calculateAndDeliverPay(Employee e) { Money pay = e.calculatePay(); e.deliverPay(pay); }
Functions That Do One Thing Cannot Be Reasonably Divided Into Sections 16 intj; for (i = 2; i < Math.sqrt(s) + 1; i++){ if (f[i]) // if i is uncrossed, cross its multiples.{ for (j = 2 * i; j < s; j += i) f[j] = false; // multiple is not prime } } // howmany primes arethere? intcount = 0; for (i = 0; i < s; i++) { if (f[i]) count++; // bump count. } int[] primes = new int[count]; // move the primes into the result for (i = 0, j = 0; i < s; i++){ if (f[i]) // if prime primes[j++] = i; } return primes; // returnthe primes } else // maxValue < 2 return new int[0]; // returnnullarrayifbad input. } } • This small mess is an example of bad coding and commenting style. • Notice that the generatePrimes function is divided into sections such as declarations, initializations, and sieve. • This is an obvious symptom of doing more than one thing. public static int[] generatePrimes(intmaxValue) { if (maxValue >= 2) // the only valid case { // declarations ints = maxValue + 1; // size of array boolean[] f = new boolean[s]; inti; // initialize array to true. for (i = 0; i < s; i++) f[i] = true; // get rid of known non-primes f[0] = f[1] = false; // sieve
Ensuring Functions Do One Thing 17 Robert Martin’s ideas around Composed Method help us apply the pattern • To write Composed Methods we have to • Divide the program into functions that do one thing • Ensure functions descend only one level of abstraction • But it is hard to know what one thing is • Actually, it is by making sure that the statements within our function are all at the same level of abstraction that we make sure our functions are doing “one thing” • But abstraction levels are fuzzy. This fuzziness is undesirable. • What we really want is an unambiguous, deterministic way to tell whether or not a function is doing one thing. • The only way to really be sure that a function does one thing is to continue to extract functions until you cannot extract any more. • After all, if you can extract one function from another, then the original function was doing more than one thing by definition. • This approach is called Extract Till You Drop
Short Methods - No Guarantees 18 • While long methods are hard to understand, short methods aren’t guaranteed to be easy to understand • When using Extract Method to break up a method into smaller pieces, you want to extract methods that are semantically meaningful, not just introduce a function call every seven lines. • The most compelling reason for keeping methods small is the opportunity to communicate with intention revealing method names. • Just splitting methods up using poor method names doesn’t help.
Short Methods A Means to an End 19 • Short methods are not an end in themselves, they are what you get when you make your code easy to understand and maintain • Short methods are just a means to the end of understandable and maintainable code • What matters is not that all our methods are shorter than some magic number • What matters is that they are so short that they can be rapidly understood, so that they are easy to maintain
How Long Should a Method be? 20 The following guideline in Code Complete is way off the mark: If you think it makes sense to make a certain routine 100, 150, or 200 lines long, it is probably right to do so…if you want to write routines longer than 200 lines, be careful…you are bound to run into an upper limit of understandability as you pass 200 lines of code. We are much more aligned with the spirit of the following opinions by influential authors in the Extreme Programming community: • I am immediately suspicious of any method with more than 5 to 10 lines. • After applying Composed Method, most of my methods are from 5 to 10 lines of code each. It should take me no more than a minute or two to understand what a developer intended the method to do. • Rarely more than 10 lines, usually about 5 lines. The typical result with Composed Method is that I can understand what a method does in a few seconds. • Four lines is OK. Maybe five. Six? OK. Ten is way too big.
Effects of Extract Till You Drop 21 • Applying Extract Till You Drop results in functions that are all about 4 lines long. • The blocks within if statements, else statements, while statements, and so on should not need braces, they should be one line long (braces are an opportunity to extract). Probably that line should be a function call. • Functions should not be large enough to hold nested structures. The indent level of a function should not be greater than one or two.
Object Oriented Decomposition Versus Functional Decomposition 22 • Surprise – pretty much all we are doing with Composed Method is classic functional decomposition (FD). • FD is useful for individual algorithms and small programs, but it does not scale up to large programs, so modularise your program using OO decomposition, and apply FD within each class. • FD is normally fine within a class, but should not be applied to very long methods in which many lines of code share many arguments and temporary variables • Far from improving communications, applying Composed Method to such methods only obscures the situation because any piece of the method you break off requires six or eight parameters. • Such methods are where classes go to hide. In such cases, you can still apply Extract Till You Drop, but instead of extracting methods, you extract classes. • Instead of applying Composed Method, use one of the following refactorings: Introduce Parameter Object, Replace Method with Method Object