Day 2 - Functions
Small
Do one thing
- Functions divided into sections clearly don’t do just one thing - avoid them
- SRP: single responsibility principle
One level of abstraction per function
- Make sure the statements within our function are all at the same level of abstraction. If not, readers may not be able to tell whether a particular expression is an essential concept or a detail
Reading code from top to bottom: the stepdown rule
- We want the code to read like a top-down narrative. We want every function to be followed by those at the next level of abstraction so that we can read the program, descending one level of abstraction at a time as we read down the list of functions
Switch statement
- It’s hard to make a small switch statement. Even a 2 blocks switch statement is larger than I’d like a single block or function to be
- By nature, switch statements always do N things. Unfortunately, we can’t always avoid switch statements, but we can make sure that each switch statement is buried in a low-level class and is never repeated. We do this with polymorphism
-
Switch can be tolerated if they appear only once and are used to create polymorphism object, and are hidden behind an inheritance relationship so that the rest of the system can’t see them
(Employee= abstract class)public Employee makeEmployee(EmployeeRecord e) throws InvalidEmployeeType { switch (e.type) { case COMMISSIONED: return new CommissionedEmployee(); case HOURLY: return new HourlyEmployee(); case SALARIED: return new SalariedEmployee(); default: throw new InvalidEmployeeType(); } }
Use descriptive names
- You know you are working on clean code when each routine turns out to be pretty much what you expected
- A long descriptive name is better than a short enigmatic one
- Be consistent in your names. A similar phraseology in names allow the sequence to tell a story
Function arguments
- The ideal number of parameters in functions should be 0
- Arguments should be instance variables (class variable, class attributes) as much as possible
- Arguments are harder even in the testing point of view
-
Using an output argument instead of return variable is confusing
void includeSetupPageInto(StringBuffer pageText)
->
StringBuffer includeSetupPage() - Flag arguments are ugly. It loudly proclaimed that the function is doing more than one thing (one if the flag is true, one if the flag is false)
-
When a function seems to need more than two or three arguments, it is likely that some of those arguments ought to be wrapped into a class of their own
Circle makeCircle(double x, double y, double radius);
->
Circle makeCircle(Point center, double radius);
-
Sometimes we want to to pass a variable number of arguments into a function. Consider the string.format method:
String.format(“%s world %.2f hours.”, name, hours);
The declaration of String.format (dyadic - 2 arguments):
String.format(String format, Object.. args);
Have no side effects
- Side effects are lies. Your function promises to do one thing, but it also does other hidden things (sometimes it will make unexpected changes to the parameters / variables of its class)
Command query separation
- Function should either do something or answer something, but not both
Prefer exceptions to returning error codes
-
When you return an error code, you create the problem that the caller must deal with the error immediately
if (deletePage(page) == E_OK) { if (registry.deleteReference(page.name) == E_OK) { if (configKeys.deleteKey(page.name.makeKey()) == E_OK) { logger.log("page deleted"); } else { logger.log("configKey not deleted"); } } else { logger.log("deleteReference from registry failed"); } } else { logger.log("delete failed"); return E_ERROR; }
->
try { deletePage(page); registry.deleteReference(page.name); configKeys.deleteKey(page.name.makeKey()); } catch (Exception e) { logger.log(e.getMessage()); }
-
Try/catch confuse the structure of the code and mix error processing with normal processing. So it is better to extract the bodies of the try/catch into functions of their own
public void delete(Page page) { try { deletePageAndAllReferences(page); } catch (Exception e) { logError(e); } } private void deletePageAndAllReferences(Page page) throws Exception { deletePage(page); registry.deleteReference(page.name); configKeys.deleteKey(page.name.makeKey()); } private void logError(Exception e) { logger.log(e.getMessage()); }
- Functions should do one thing. Error handling is one thing