Stop that!


Published on

Slides from talk given at SDEC2013.

Published in: Technology
  • Be the first to comment

  • Be the first to like this

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide
  • Welcome to my talk: Questioning Dogmatic Programming, or, “things I wish my coworkers would stop doing”. This is a a semi-serious talk about lots of programming practices that are taken for granted, but that I think should be avoided at all costs.
  • What form does dogmatism take in the programming community?<read list>Considering programming is a logical, rational activity people sure have a lot of rigid beliefs. You’ll never find justification provided in similar strength to these claims.
  • Here’s a butchered quote that has guided my personal philosophy regarding so-called rules, idioms, best practices, whatever you want to call them.<quote>So what rules should we be following and what rules should we be breaking?
  • Not saying all dogma is bad: there are lots of good examples.Formatting code: no reason not to, tools do it automatically, improves readabilityThings like using camelcase for methods, don’t allow multiple returns, whatever. Improves readability among the team and we don’t waste time thinking about trivial things.All things being equal: less code is better than more code, and simpler designs are better than complex design.Everybody should be refactoring their code as they go.Modular fashion, parts of the system are essentially black boxes that talk to each other. Can't think of a reason not to do this.
  • Here are some more statements that are broadly accepted as true but are actually almost always wrong.<read the list to prime people>In this talk we’ll go over some situations where dogmatic adherence to these principles will get you into lots of trouble. Or at least get the future maintainers of your code into trouble.
  • Okay, let’s start with comments. Every programming language has this neat feature: you can put whatever you want after or in between certain characters. Then, using the syntax highlighting, readers are trained to ignore blue and green text. It’s really handy.
  • Seriously, though: first get introduced to dogmatic thinking in education: always comment your code. Lose marks for uncommented code, or profs give you standard headers describing inputs, outputs etc. and you have to fill them in.Intent: what the programmer was trying to accomplishTricky: if code is non-obvious, does something unusual, or is otherwise unclear, comments can elaborateAPI/interface: for code intended for re-use such as APIs or interfaces, comments provide examples of use, what parameters to pass, what the structure of results are, etc.One argument says: code is literal; can only explain what is going on: not why it needs to be done.
  • Very clear what this code is doing, but why is it doing it? What business rule can explain it?
  • Might be tempted to throw in a comment to explain why we need to send the cheque…
  • Stop doing this! Already have something that communicates what the programmer is trying to do… the code!Repetitious (violates DRY) to simply re-state what the code is doing, even if you add extra information or summarize.
  • Oops, the code is working correctly but the comment is no longer accurate. At best, people will ignore the comment, or at worst somebody reading it may be misled. Might try to track down a bug and assume that the problem lies with the person’s age. <explanation of wrongness ahead>
  • What else can go wrong?Skipped: refactoring tools won’t update comments, and some people use them blindly without checking updated code (e.g. rename method at 100 call sites… are you going to check them all?)Not-essential: Poorly written: we’re hired as programmers, not writers. Lots of comments are just badly written, full of typos. Don’t give you much confidence.Ignored: some people like myself have been burned so many times by bad comments that they are just trained to ignore them entirely. <anecdote about not seeing Dave’s comment block in rails partial)>. So they end up as a waste of your time to write and waste of space.
  • So how to we eliminate these sorts of comments? Each of these refactorings takes a comment and embeds it into the program as a symbol, which you are forced to read as part of the program. They are refactorable, can be as descriptive, and leave the code cleaner than comments.
  • In this refactoring, we give method a name to include additional meaning, or why it's being done.This embeds the “why” directly in the code and improves the readability of the original code.Maintainers are practically forced to read the method name, and it occurs in multiple places, so the code isless likely to be modified such that the method name is invalid. I’ve seen it happen, but not nearly as often.
  • more compact than the "comment as method" refactoring, but equally descriptive. Good for one-offs.
  • Constant helps us to understand the conditional check - it's a minimum so we use greater than or equal.With all of these refactorings, there’s no reason to use short names. We have fancy autocompletion from text editors like vi all the way to IDEs like Eclipse.
  • Using a language (Ruby) that people aren’t likely to be familiar with to illustrate the point.If you write clever or tricky code, sometimes you get this feeling, like an itch, telling you “oh, I should add a comment”.
  • Stop! This is a product of dogma.Instead, interpret that itch or feeling as a sign that you need to refactor, simplify, rename, whatever it takes to make the code’s purpose and function obvious.Comments are admissions of failure to express your intent in code.When you add an explanatory comment, it's because you weren't clear enough using the programming language, so you fall back to the comfort of a natural language.
  • <explain code>Maintains the original cleverness, but the reader can tell at a glance what the purpose is, and has a better chance of being able to understand how the two steps work. It’s a trivial example, but you can blow it up to larger sizes and the idea is the same. You should always attempt to simplify/refactor/rename/etc. before falling back to comments.
  • The last reason you might want to add comments to your code: it’s intended for re-use. Generally a collection of classes, methods, utils. If you want to get fancy, can publish documentation using a tool like rdoc or javadoc.Might be compelled to add comments to achieve a higher level of qualitythan regular code. Feels more complete, done due diligence. You just have this feeling that high quality code should have lots of comments. No, you can’t explain it. That’s kind of a bad sign…
  • <explain code>Obviously this isn’t very descriptive on its own. You can’t push much meaning into constants, method names or locals here… you decide: give in, have to add a comment.What form might that comment take? Here’s a hint: after learning about what a library generally does, what is the next step you take? …. <code example upcoming>
  • Look for code examples! So write some code examples for your API. Good examples should demonstrate what valid parameters are and what the return values look like.So here’s one approach: a code example embedded into a comment. Here, the example has been marked up with HTML so it displays nicely as Javadoc. Usage of the interface is nicely illustrated for the reader, at the source.Looks like we’ve got this one solved, right?
  • No, not solved at all. This is even worse than the comments we looked at before… Because the interface definition is in a different file, it’s likely somebody will miss the comment entirely when changing the impl.Another issue, you’re mixing three languages together: HTML, English and Java, which is a maintenance headache. I think we’ve all been in the situation where we’re manually formatting a code snippet in a comment by spacing every line just so…Finally, the compiler isn’t checking your comment so make sure it compiles, and even if it does, there’s no guarantee it will work. Code examples in comments might become obsolete and omit key steps.So how do we keep our example code and resolve these issues? Well, we have to get the compiler to check it. It also has to be in the source repository so other developers can find it. And lastly, it also has to be periodically executed to make sure it works.To some of you, that probably sounds like a real headache. Hopefully the rest of you are thinking: that sounds a lot like a test.
  • You’re right, I am talking about tests! So let’s document the usage of our API with a test. We have everything that useful documentation should have:1) input: a string2) output: a list of people3) what the method does: appears to be matching on the people’s first and last namesWe know it’s valid code as it is checked by the compiler. As an example, if the test passes, it very likely to be a valid use case. There are also likely to be multiple examples testing different cases, as good tests typically exercise all parts of the code.If all API documentation came in the form of runnable tests, I would be pretty happy.----- Meeting Notes (2013-10-17 23:54) -----make them "live" -> make sure they compilewant to execute them -> make sure they actually worktest -> thing that you're already writing anyway, so no extra work. fix code exampleknow code example works -> running tests all teh timetests make better API documentation than inline comments----- Meeting Notes (2013-10-18 13:12) -----fix last bracket
  • I’d like to clarify: not saying “don’t write documentation”, I’m just talking about inline comments.Documentation in general is much broader, and includes lots of good stuff <examples above>. Write code when communicating with programmers, and save English and diagrams for non-programmers.
  • As a final note about comments, I'd like to bring out this fabulous snippet of code, which I've seen countless variations of over the yearsI'd like to think stuff like this is written by people silently rebelling against poor coding standards, or planted there by people like me who secretly hate comments, because I'd be depressed to find out that somebody did this and actually thought they were being helpful.
  • So please, stop writing comments! Unless you are being paid by the line. Then right on <thumbs up>
  • Next, let’s talk about optimization: making code go faster at the expense of other concerns.
  • There are good reasons to optimize. But, most of the time our efforts are wasted, because we've convinced ourselves that we know where the important bottlenecks are, but in reality we don't.
  • Going to go through one example,drawn from a recent code review.<explain code>Somebody might look at this and think: hey, you’re iterating over the list twice! These two loops could be combined and that would make this go a lot faster.----- Meeting Notes (2013-10-17 23:54) -----"some recent code review feedback"
  • The only obvious way to me to combine the loops is to inline the two methods and mix up the sums and calculations.
  • Slow down!Now the calculate method has two new problems:1) It’s doing more than one thing.2) The method is much longer and harder to decompose.2) It’s operating at multiple levels of abstraction: not only does it have to know about all the averages we want to calculate, it has to know how to calculate them!Okay, so we’ve mangled the code, but at least it's faster right?
  • An extra loop over a million objects is 3.6 milliseconds, nothing to get worked up about, and certainly not worth sacrificing the readability and maintainability of the code. 3.5 ms -> UI, imperceptiblebatch job -> irrelevantIntegration with other systems, e.g. database -> those calls will greatly outweigh thisThe point isn’t that looping over a collection multiple times is okay. The point is that there’s nothing wrong with wasting some cycles and putting readability first. Programmer time is more expensive than computer time. Also, small code optimizations like this are almost never worthwhile.Another way to reiterate this similar to what I said about comments: write code for people first, computers second.
  • I feel like this quote is in the collective consciousness, but people choose to ignore it anyway.Maybe folks don’t realize how broad the advice is intended to be, so I’ll expand on it a bit:1) What is ‘premature’? - before the feature has been determined to be too slow. What’s too slow? Need to answer this question. Set performance goals for max response time. Maybe you never come near them and so no optimization required. - before the code has been profiled - before you have determined whether the code will be kept or not (i.e. don’t waste time polishing temporary or volatile code)2) What is ‘optimization’? -thinking of bit twiddling? Other low level stuff? No, it’s designing or modifying code with performance as the foremost consideration. Using these definitions, you pretty much never need to optimize.
  • Let’s talk about program flow, specifically conditional statements.
  • What’s wrong with these things? We’re almostnever told to avoid them. Everybody uses them. What’s the problem? We need to use them, right?
  • One of those things that people do without thinking about it. Have to check for nulls, because somebody might pass them into our method, or because another method might return one. Stuff like this ispretty common
  • You might be thinking: wait, what’s wrong with that code? We have to check for nulls, and they make a really convenient value to indicate “no result”.The problem:1) Null checks violate DRY: every time you check the same return value for null, you’re essentially saying over and over again that some value X could be null. And you’re likely to miss one.2) Null checks are sometimes unnecessary. Such checks at best add clutter, and at worst mislead readers or cause them to question their understanding of code.
  • Here’s a neat idea: the null object pattern. replace nulls with objects containing “empty” behaviour.In this case, an empty list makes a natural null object. <explain code>Now callers can just iterate over the list without checking for null.
  • Another application of the Null Object Pattern with a custom type that has empty behaviour defined. <explain code> It might be be natural to simply null here, but if what you really want to do is nothing, use a null object to represent a missing command. Then callers don't need to worry about null checks. You can also log calls to execute or print a stack trace similar to what you would get if a NPE was thrown.
  • Another option for eliminating null checks: the option type.Lets you wrap nullable values like objects, strings, etc. Provides multiple benefits:1) Encodes the fact that the value might be null using the type system, which forces you to deal with that possibility by going through Option rather than remembering to sprinkle null checks everywhere.2) Provides mechanism for transforming or using the value wrapped in a new Option (via map, passing in a transformation function).3) Can elegantly return the value or if missing, a default (via orSome) without a conditional.4) Can query it to see if the value is null, so at worst you’re just in the same boat as you were before<explain code>
  • <explain how it works>TODOadd parseNames4 using map??
  • As an example of another case where you might want to use conditional statements, suppose you want to create a method for sampling weather data from a 3rd party library.You think all of these different methods are kinda clunky, so you want to replace them with a single, generic call.
  • might be thinking: what the heck is wrong with that?Having just one switch statement isn’t awful, but when you add enums, checks with switch or if tend to pop up everywhere. This is just repeating the knowledge of what the complete list is in multiple places. This means adding new sensor types becomes difficult as all of these will need to be located and updated. Adding new behaviour based on type (e.g. print with units) isn’t much better : copy & paste switch and modify
  • <title>: Fancy way of saying: push the differing behaviour into method of a class hierarchy.A fairly well-known refactoring:This works out quite nicely in Java with a rarely used feature and just a bit of boilerplate, but all of the switch statements’ behaviour can be consolidated into a single location, and no conditionals are required at all.<explain how it works as a replacement>TODO: add revised readsensor (one-liner)
  • In situations where we can’t imbue our enumerated types with methods, we can still achieve the same effect by creating a wrapper hierarchy around the sensor package that has polymorphic behaviour. <explain code> This solution adds more boilerplate still, but we get most of the benefits:1) elimination of all but one switch2) consolidation of the different behaviours into one place3) compiler errors if new behaviours are requiredMight not look great right now, but when you need to add new types or behaviours, it'll pay off
  • I’m sure we’ve all written code like this at some point, and it's kind of embarassing. Very clumsy, just go through every condition to get an answer. This sort of code makes it hard to test, hard to maintain, hard to reason about; especially if later checks depend on earlier ones being false.There’s no magic bullet to refactor stuff like this. Could use a map from province to tax to replace some of it. Either way you should try to avoid writing if statements that invite this kind of behaviour.
  • A good start would be “replace conditional with map”. This refactoring replaces the conditions with a map key, and the assignment with a value from a map. Most of the if statement is eliminated.You could take this further by moving some logic into the enums if appropriate, or splitting up the tax into pst and gst, or extending the map to work on pairs of province and product.
  • If you think I’m being extreme, there’s an entire campaign organized around the elimination of conditional statements altogether.
  • Some further arguments against conditionals:1) Adding conditional statements can exponentially increase the number of paths through code. (e.g. 8 independent if statements results in 256 unique paths!) One measure of code paths is called cyclomatic complexity. This is a well-known issue and there are tools you can use to enforce per-method limits on cyclomatic complexity, and you should set the limit as low as is bearable.2) If-else chains invite people to simply add additional cases to meet needs.3) If you are switching on enum values or various subtypes of something, it’s a missed opportunity to use polymorphism, and we’ve already seen how that works4) Writing tests against code with lots of conditionals is tedious if you want 100% coverage, and the tests often end up full of duplication.So try some of the previous techniques and refactorings before adding if statements to your programs. I’ll talk more about this in the next section…
  • Next I’d like to talk about error handling.
  • Here’s some beginneradvice: make sure your programs can handle all of the errors thrown at it. This code might be a bit exaggerated, but it’s common.
  • Stop doing this! This code is a sprawling, confusing mess at least compared to what it could be. Null checks everywhere and all possible exceptions are caught. Code like this is very common, and people seem to write it without thinking. Yet you might even be proud because you "caught all the errors", good job. Unfortunately, there are some problems with coding this way:Most of the code is fluff, not related to the work we are trying to do. We justify this by saying it is necessary, but I'll show you why not in a bit.2) If not finding the image was actually an error case, best case the code doesn’t do anything, and worst case, the error causes an exception later on, but now the original cause has been obfuscated by our misguided attempt to handle all errors.3) Our methods tend up being responsible for multiple things: their primary task, and handling errors. This mixing of responsibilitiesmakes it hard to figure out what code is doing.4) Sometimes duplicate action may be taken. Countless times I’ve seen exceptions double-logged, which can be confusing.
  • Error checking should exist at two levels in the system:First, errors and exceptions should be initially detected at the lowest possible level, only if necessary, and then thrown and unwind through the entire app. Most of the app shouldn’t even care about errors at all. If third party code throws an exception, just let it go. Don’t bother catching it. Most of the time we can’t or shouldn’t attempt to recover.Second, at the top-most level where we can centralize error handling", all exceptions should be caught and logged. Top-level depends on type of app, e.g.1) webapp, this might be in the initial request handler, often frameworks provide a hook for dealing with exceptions2) command-line apps, this will likely be in or close to the main method3) GUI app, this will be in the event dispatcher. The common theme is: decide what to do with exceptions at a single point, and avoid duplication and clutter.So how do we implement this architecture?
  • Checked exceptions are dumb. Why? The language is forcing you to either handle errors immediately even if it doesn’t make any sense, or to clutter up your method declarations with lists of checked exceptions that may be thrown.If your language has checked exceptions, start by writing wrappers against APIs that throw them to pass the results through and wrap the exception in an unchecked variety.For example, let’s wrap the and make the exception unchecked. You could even create some new exception types if you want to (e.g. UncheckedIOException)
  • Here, we’ve made use of our wrapper to simplify our original image reading code. The behaviour did change, as now we’re passing the exception off to be handled elsewhere. We don’t care anymore. The decision about how to handle the error is no longer at this level.We’ve still got that pesky map lookup to deal with, so let’s fix that next.
  • We add a new exception type to represent missing image keys, then create a method to try and read the key from the map, and fail with an exception.This gets back to what I was saying earlier about null checks: don’t even let this method return null and it will simplify all of the calling code. When you can’t recover, exceptions are superior to returning error values.
  • Our original code has been reduced to just the important stuff and all of the details of error handling have been pushed down to lower levels of abstraction. The exceptions can now be caught and handled at a higher level.
  • So what’s the point?Maybe you’ll leave this talk thinking “wow I’m never going to use conditionals or nulls ever again!” Well, no, that’s actually the opposite of what I want. I may have used some strong language, but you shouldn’t always do what I say, for the exact same reasons you shouldn’t always follow existing dogma. Instead, if you just pause and think before writing a switch statement or adding a long-winded comment to some code, that’s great.Maybe you disagree with me, and that’s good too! If you disagree, at the very least you understood what I was saying, spent an hour thinking about your craft and came to your own conclusions. Hopefully the techniques and refactorings I’ve demonstrated will help you to improve your code, but if not, hopefully I’ve got you started down the path of finding ones that will.
  • Or maybe the best I can hope for is that you heed the advice given by this user of StackExchange: <quote>Best practices, dogma, design patterns or what have you are not a substitute for careful thought. Instead of getting hung up on particular techniques or idioms, just be do whatever makes sense, and then be consistent.
  • The goal with most of this talk is to encourage you to write code that is readable by people first. This isn’t easy, so here are some handy books.- clean code – PDF available free online, paper copy available.- pragmatic programmer – ebook or paper, a classic full of great principles and practices- code complete – exhaustive, covers everything. E.g. entire chapter devoted to variable names. Would be hard pressed to read it front to back but it's a good addition to the bookshelf.
  • Thank you!
  • Stop that!

    1. 1. Stop That! Questioning Dogmatic Programming or Things I Wish My Coworkers Would Stop Doing Doug Hiebert Thank you to our Sponsors
    2. 2. About Me Doug Sparling / Doug Hiebert Software Dev - Protegra Twitter: @doughiebert Email:
    3. 3. Dogmatism noun 1. The tendency to lay down principles as undeniably true, without consideration of evidence or the opinions of others.
    4. 4. Programming Dogma • • • • “A, B and C are best practices” “X considered harmful” “Y is evil” “language/technology/tool Z is the best”
    5. 5. Programming Dogma “Break any of these rules sooner than [code] anything outright barbarous.” -- George Orwell, "Politics and the English Language," 1946
    6. 6. Good Programming Dogma • • • • • Format your code Have a coding standard Prefer simplicity Refactor continuously Use modular design
    7. 7. Bad Programming Dogma • • • • Comment your code Don’t waste cycles Decide with conditionals Handle all errors
    8. 8. Comment Your Code /** * @return Always returns true. */ public boolean isAvailable() { return false; // FIXME: temporary testing }
    9. 9. Comment Your Code • Communicates programmer intent - The “why” to the code’s “what” • Explains tricky/clever code • Explains how to use APIs/interfaces
    10. 10. Communicating Intent for (Person person : people) { if (person.getAge() >= 65) { sendCheque(person.getAddress()); } }
    11. 11. Communicating Intent for (Person person : people) { // send cheque to persons old enough // to qualify for pension payout if (person.getAge() >= 65) { sendCheque(person.getAddress()); } }
    12. 12. Communicating Intent for (Person person : people) { // send cheque to persons old enough // to qualify for pension payout if (person.getAge() >= 65 || person.getYearsOfService() > 30) { sendCheque(person.getAddress()); } }
    13. 13. Communicating Intent • • • • Skipped by refactoring tools Not essential for program function Poorly written or misleading Some people ignore them (like me!)
    14. 14. Comment-Eliminating Refactorings • Comment as Method • Comment as Local Variable • Comment as Constant
    15. 15. Comment as Method for (Person person : people) { if (oldEnoughForPensionPayout(person)) { sendCheque(person.getAddress()); } } ... boolean oldEnoughForPensionPayout(Person p) { return p.getAge() >= 65; }
    16. 16. Comment as Local Variable for (Person person : people) { boolean oldEnoughForPensionPayout = person.getAge() >= 65; if (oldEnoughForPensionPayout) { sendCheque(person.getAddress()); } }
    17. 17. Comment as Constant private static final int MIN_AGE_FOR_PENSION_PAYOUT = 65; for (Person person : people) { if (person.getAge() >= MIN_AGE_FOR_PENSION_PAYOUT) { sendCheque(person.getAddress()); } }
    18. 18. Explain Tricky/Clever Code # calculates the average a = vals.inject(0.0) { |s, v| s + v } / vals.size
    19. 19. Explain Tricky/Clever Code sum = vals.inject(0.0) { |total, val| total + val } average = sum / vals.size
    20. 20. Explain APIs/Interfaces • Code intended for re-use by others • Library of classes, methods, utilities, etc. • RDoc, Javadoc, etc.
    21. 21. Explain APIs/Interfaces interface PersonRepository { Set<Person> findPeople(String text); ... }
    22. 22. Explain APIs/Interfaces /** * <p><b>Usage:</b></p> * * <pre> * PersonRepository exampleRepo = makeRepository(bobJones, joelStevens, shirleySmith); * * List<Person> persons = exampleRepo.findPeople("jo"); * * persons.get(0); // bobJones * persons.get(1); // joelStevens * </pre> * * @author Doug */ interface PersonRepository { List<Person> findPeople(String text); ... }
    23. 23. Explain APIs/Interfaces @Test public void testFindPeopleByFirstAndLastName() { Person bobJones = testPerson("Bob Jones"); Person joelStevens = testPerson("Joel Stevens"); Person shirleySmith = testPerson("Shirley Smith"); PersonRepository exampleRepo = makeRepository( bobJones, joelStevens, shirleySmith); List<Person> persons = exampleRepo.findPeople("jo"); assertEquals(bobJones, persons.get(0)); assertEquals(joelStevens, persons.get(1); }
    24. 24. Documentation • Comments are a type of documentation • Good examples of documentation: - User’s guide/manual - Architecture diagrams - Decision log
    25. 25. A Final Note About Comments /** * The person's name. */ private String name; ... /** * Returns the person's name. * * @return the person’s name */ public String getName() { return name; }
    26. 26. Don’t Waste Cycles • Improve UI response • Speed up long-running batch jobs • Satisfaction of “eliminating waste”
    27. 27. Don’t Waste Cycles void calcAverages1(List<Person> people) { double averageAge = averageAge(people); double averageHeight = averageHeight(people); ... } double averageAge(List<Person> people) { long ageSum = 0; for (Person person : people) { ageSum += person.getAge(); } return (double) (ageSum / people.size()); } double averageHeight(List<Person> people) { /* as above */ }
    28. 28. Don’t Waste Cycles void calcAverages2() { long ageSum = 0; long heightSum = 0; for (Person person : people) { ageSum += person.getAge(); heightSum += person.getHeight(); } double averageAge = ageSum / people.size(); double averageHeight = heightSum / people.size(); ... }
    29. 29. Don’t Waste Cycles • Microbenchmark parameters: - 1,000,000 people - 1500 iteration warm-up - 10 runs (averaged) • calcAverages1: 7237μs • calcAverages2: 3640μs • Roughly 100% slower (as expected) • Only 3.6ms (imperceptible)
    30. 30. Don’t Waste Cycles “Premature optimization is the root of all evil” -- Donald Knuth
    31. 31. Decide with Conditionals • if, unless, else, ?:, etc. - Implement conditional logic - Handle errors - Null checking • switch, case, etc. - exhaustively handle different cases
    32. 32. Decide with Conditionals public List<String> parseNames(String names) { if (names == null) { return null; } return Arrays.asList(names.split(",")); } void readNames() { String line = System.console().readLine(); List<String> names = parseNames(line); if (names != null) { for (String name : names) { // ... } } }
    33. 33. Null Object Pattern public List<String> parseNames2(String names) { if (names == null) { return Collections.emptyList(); } return Arrays.asList(names.split(",")); }
    34. 34. Null Object Pattern public class CommandLookup { public Command findCommand(String command) { if (validCommand(command)) { // ... } else { return new NullCommand(); } } public class NullCommand implements Command { public void execute() { // do nothing } } }
    35. 35. Option Type Option<String> aString = Option.some("string"); Option<String> noString = Option.none(); F<...> length = new F<String, Integer>() { public Integer f(String str) { return str.length(); } };; // 6; // 0
    36. 36. Option<T> Type public List<String> parseNames3( Option<String> names) { return Arrays.asList( names.orSome("").split(",")); }
    37. 37. Decide with Conditionals interface SensorPackage { int getTemperature(); int getHumidity(); int getPressure(); }
    38. 38. Decide with Conditionals enum SensorType { TEMPERATURE, HUMIDITY, PRESSURE; }
    39. 39. Decide with Conditionals public int readSensor(SensorType sensorType) { switch (sensorType) { case TEMPERATURE: return weatherSensor.getTemperature(); case HUMIDITY: return weatherSensor.getHumidity(); case PRESSURE: return weatherSensor.getPressure(); default: throw new IllegalArgumentException("..."); } }
    40. 40. Replace Conditional with Polymorphism enum SensorType { abstract int getReading(SensorPackage sensors); TEMPERATURE { public int return } }, HUMIDITY { public int return } }, PRESSURE { ... }; } getReading(SensorPackage sensors) { sensors.getTemperature(); getReading(SensorPackage sensors) { sensors.getHumidity();
    41. 41. Replace Conditional with Polymorphism abstract class SensorWrapper { abstract int getReading(); static class TemperatureSensorWrapper extends SensorWrapper { private SensorPackage sensorPackage; public TemperatureSensorWrapper(SensorPackage sensorPackage) { /*...*/ } int getReading() { return sensorPackage.getTemperature(); } } static SensorWrapper create( SensorType sensorType, SensorPackage sensorPackage) { switch (sensorType) { case TEMPERATURE: return new TemperatureSensorWrapper(sensorPackage); case ... default: throw new IllegalArgumentException("..."); } } }
    42. 42. Decide with Conditionals if (province == Province.MB && productType == ProductType.WIDGET) { tax = 0.05; // no PST on widgets in MB } else if (province == Province.MB) { tax = 0.13; } else if (province == Province.AB) { tax = 0.05; } else if (...) { // and so on } else if (...) {
    43. 43. Replace Conditional with Map static { taxByProvince.put(Province.MB, 0.13); taxByProvince.put(Province.AB, 0.05); // ... etc. } if (province == Province.MB && productType == ProductType.WIDGET) { tax = 0.05; // no PST on widgets in MB } else { tax = taxByProvince.get(province); }
    44. 44. Anti-If Campaign
    45. 45. Anti-If Campaign • • • • Cyclomatic complexity if-else chains invite trouble Polymorphism more appropriate Testing is tedious
    46. 46. Handle All Errors private Map<String, File> imagesMap = ... ... Image image = null; File imageFile = imagesMap.get("expectedImage"); try { if (imageFile != null) { image =; } } catch (IOException e) { // image could not be read } if (image != null) { // do something with image }
    47. 47. Handle All Errors High Level main(...) { try { ... } catch { ... } } ... ... ... Low Level throw e; ... ... ... ...
    48. 48. Wrap Exception static class ImageUtil { static BufferedImage read(File imageFile) { try { return; } catch (IOException e) { throw new RuntimeException(e); } } }
    49. 49. Wrap Checked Exception private Map<String, File> imagesMap = ... ... File imageFile = imagesMap.get("expectedImage"); Image image = null; if (imageFile != null) { image =; } // do something with image
    50. 50. Push Error Handling Down public class ImageKeyNotFoundException extends RuntimeException { ... } public File getImageFile(String imageKey) { if (imagesMap.get(imageKey) == null) { throw new ImageKeyNotFoundException( "image key not found: " + imageKey); } return imagesMap.get(imageKey); }
    51. 51. Push Error Handling Down File imageFile = getImageFile("expectedImage"); Image image =; // do something with image
    52. 52. In Conclusion
    53. 53. In Conclusion “The only „best practice‟ you should be using all the time is „Use Your Brain‟” -- Steven Robbins, StackExchange
    54. 54. More Information / Next Steps • Clean Code (free!) • Pragmatic Programmer (classic!) • Code Complete 2nd Edition (thick?)
    55. 55. Thank You! @doughiebert Thank you to our Sponsors