Clean code _v2003

Post on 09-Jul-2015

284 views 1 download

description

Slides writing clean code. Inspired by Robert C. Martins book "Clean Code: A Handbook of Agile Software Craftsmanship".

Transcript of Clean code _v2003

Design Patterns in Visual Basic ???

Criteria used in defining clean codeThe code is mostly read and maintained, not

written, so:› SHOULD BE LITERATE (Reads like prose)

But also:› Elegant› Unit-testable› Has minimal dependencies› Clear and minimal API› One way rather than many to do things› Good error handling› Contains minimal duplication› Efficient

Meaningful names

Functions

Comments

Use intention revealing names

public List<int[]> getThem() {List<int[]> list1 = new ArrayList<int[]>();for (int[] x : theList)

if (x[0] == 4) list1.add(x);return list1;

}

› Problems : What kinds of things are in theList? Significance of the zeroth subscript? Significance of the value 4? The most significant problem is actually the

IMPLICITY of the code! (the code assumes we know the answers to questions like the ones above).

Refactored version (improved, yet not perfect):

public List<int[]> getFlaggedCells() {List<int[]> flaggedCells = new ArrayList<int[]>();for (int[] cell : gameBoard)

if (cell[STATUS_VALUE] == FLAGGED) flaggedCells.add(cell);

return flaggedCells;}

Make meaningful distinctions

public static void copyChars(char a1[], char a2[]){for (int i = 0; i < a1.length; i++) { a2[i] = a1[i];}

}

› Problems: a1, a2 have no meaningful names. A better version would replace the variable

name a1 and a2 with source, respectively destination.

Use pronounceable names

class DtaRcrd102 {private Date genymdhms;private Date modymdhms;private final String pszqint = "102";

};

Refactored version:

class Customer {private Date generationTimestamp;private Date modificationTimestamp;private final String recordId = "102";

};

Avoid encodings

› Hungarian notation Was considered important in the Windows C

API, for example - everything was an integer handle, long pointer or void pointer.

The biggest issue: the compiler did not check the type of a variable.

› With modern compilers AND modern programming practices:

String strName = “name”; // OBSOLETE, REDUNDANT, CLUTTERS UP THE CODE

Member prefixes

public class Part {private String m_dsc;

void setName(String description) {m_dsc = description;}

}

public class Part {String description; // What's wrong with this?

void setDescription(String description) { this.description = description;}

}

Avoid mental mapping

URL p; // an url used in the application

› Problems Readers shouldn't have to mentally translate

the name p into its meaning. A single-letter name is a poor choice; it’s just a

place holder that the reader must mentally map to the actual concept.

› One difference between a smart programmer and a PROFESSIONAL programmer is that the professional understands that clarity is king. Professionals write code that others can understand.

The first rule of functions:› SMALL

Functions should not be 100 lines long. Functions should hardly ever be 20 lines long. Smallness implies that blocks within if

statements, else statements, while statements, etc should be one line long, that line should be a function call.

functions should not be large enough to hold nested structures.

Therefore the indent level of a function should not be greater than one or two.

› This rule, of course, makes the functions easier to read and understand.

The second rule of functions:

› DO ONE THING We write functions to: decompose a larger

concept (i.e. the name of the function) into a set of steps at the next level of abstraction.

The third rule of functions:› NO MORE THAN THREE ARGUMENTS

the ideal number of arguments for a function is zero (niladic).

Next comes one (monadic), Followed closely by two (dyadic). Three arguments (triadic) should be avoided

where possible. More than three (polyadic) requires very

special justification, and then shouldn’t be used anyway.

Output Arguments› Arguments are most naturally interpreted as

inputs to a function.

appendFooter(s);

› To understand the code, we look at the method signature

public void appendFooter(StringBuffer report) { }

Command query separation

public boolean set(String attribute, String value) { }

› This leads to odd statements like this:

if (set("username", "unclebob")) { }

› What does this mean?

› Separate the preceding function in two, a command and a query:

if (attributeExists("username")) {setAttribute("username", "unclebob");

}

No side effectspublic class UserValidator {

private Cryptographer cryptographer;public boolean checkPassword(String userName, String password) {

User user = UserGateway.findByName(userName); if (user != User.NULL) { String codedPhrase = user.getPhraseEncodedByPassword(); String phrase = cryptographer.decrypt(codedPhrase, password); if ("Valid Password".equals(phrase)) {

Session.initialize(); return true;

}}return false;

}}

› Problem: the side effect represented by the call to Session.initialize().

Prefer Exceptions to Returning Error Codes

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");

› Problems: Returning error codes from command

functions is a subtle violation of command query separation: if (deletePage(page) == E_OK)

Leads to deeply nested structures.

› Possible refactoring

try {deletePage(page);registry.deleteReference(page.name);configKeys.deleteKey(page.name.makeKey());

}catch (Exception e) {

logger.log(e.getMessage());}

Comments Do Not Make Up for Bad Code

// Check to see if the employee is eligible for full benefitsif ((employee.flags && HOURLY_FLAG)

&& (employee.age > 65))

› What about this?

if (employee.isEligibleForFullBenefits())

Good comments› Informative comments

// format matched kk:mm:ss EEE, MMM dd, yyyyPattern timeMatcher = Pattern.compile(

"\\d*:\\d*:\\d* \\w*, \\w* \\d*, \\d*");

› Explanation of intent

public int compareTo(Object o){if(o instanceof WikiPagePath){ WikiPagePath p = (WikiPagePath) o; String compressedName = StringUtil.join(names, ""); String compressedArgumentName = StringUtil.join(p.names, ""); return compressedName.compareTo(

compressedArgumentName);}return 1; // we are greater because we are the right

type.}

› Warning of consequences// Don't run unless you// have some time to kill.public void _testWithReallyBigFile() { writeLinesToFile(10000000);}

› Amplification

Bad comments› Unclear explanation

public void loadProperties(){

try{ String propertiesPath = propertiesLocation + "/" + PROPERTIES_FILE; loadedProperties.load(propertiesPath);catch(IOException e){ // No properties files means all defaults are loaded}

}

› Redundant// Utility method that returns when this.closed is true. Throws an

// exception if the timeout is reached. public synchronized void waitForClose(final long timeoutMillis) throws Exception {

if(!closed){ wait(timeoutMillis); if(!closed) throw new Exception("MockResponseSender could not be

closed");}

}

// ContainerBase.java taken from Tomcatpublic abstract class ContainerBase

implements Container, Lifecycle, Pipeline, MBeanRegistration, Serializable {

/*** The processor delay for this component.*/protected int backgroundProcessorDelay = -1;

/*** The lifecycle event support for this component.*/protected LifecycleSupport lifecycle = new LifecycleSupport(this);

› Mandated Comments

/** * * @param title The title of the CD * @param author The author of the CD * @param tracks The number of tracks on the CD * @param durationInMinutes The duration of the CD in minutes */public void addCD(String title, String author, int tracks, int durationInMinutes) {

CD cd = new CD();cd.title = title;cd.author = author;cd.tracks = tracks;cd.duration = duration;cdList.add(cd);

}

› Noise comments

/** * Default constructor. //Oh, really? */

protected AnnualDateRule() {}

/** The day of the month. */private int dayOfMonth;

/** * Returns the day of the month. * * @return the day of the month. */public int getDayOfMonth() {return dayOfMonth;}

› Commented out code

InputStreamResponse response = new InputStreamResponse();response.setBody(formatter.getResultStream(),

formatter.getByteCount());// InputStream resultsStream = formatter.getResultStream();// StreamReader reader = new StreamReader(resultsStream);// response.setContent(reader.read(formatter.getByteCount()));

Why are those two lines of code commented? Are they important? Were they left as reminders? No one will delete them, they will rot in the code (sometimes for years).

Surprises coming up....

Urmeaza...poze de la manastire...