the evolution of good code
DESCRIPTION
A talk describing how best practices for writing code have changed throughout the last 21 years. What do we think of as good code, and how has our perception of good code changed form the early days of programming to the exciting times we live in now? In this talk we do a bit of digging into the short history of programming, by looking at the available literature and various best practices promoted throughout the ages to improve your code.TRANSCRIPT
The bookshelf“A man may as well expect to grow stronger by always eating as wiser by always reading.”!— Jeremy Collier
Writing Solid Code
❖ Steve Maguire!
❖ Microsoft Programming!
❖ 1993!
❖ “Microsoft’s Techniques for Developing Bug-Free C Programs”
The Pragmatic Programmer
❖ Andrew Hunt & David Thomas!
❖ Addison Wesley!
❖ 2000!
❖ “examines the core process - taking a requirement and producing working, maintainable code that delights its users”
Code Complete
❖ Steve McConnell!
❖ Microsoft Programming!
❖ 2004 (1st Edition 1993)!
❖ “A Practical Handbook of Software Construction” - “Helps you build the highest quality code”
Clean Code
❖ Robert C. Martin (“Uncle Bob”)!
❖ Prentice Hall!
❖ 2009!
❖ “A handbook for Agile Software Craftsmanship” — “How to write good code and how to transform bad code into good code”
97 Things Every Programmer Should Know
❖ Various Artists, edited by Kevlin Henney!
❖ O’Reilly!
❖ 2010!
❖ “Collective Wisdom from the Experts” — “you’ll expand your skills by … learning appropriate best practices”
Growing Object-Oriented Software, Guided by Tests
❖ Steve Freeman & Nat Pryce!
❖ Addison Wesley!
❖ 2010!
❖ “Two TDD pioneers show how to let tests guide your development and ‘grow’ software that is coherent, reliable, and maintainable”
The Art of Readable Code
❖ Dustin Boswell & Trevor Foucher!
❖ O’Reilly!
❖ 2011!
❖ “Simple and Practical Techniques for Writing Better Code”
Guess the book!“If we encounter a man of rare intellect, we should ask him what books he reads.”!— Ralph Waldo Emerson
2011
2009
! ! 1993
Use Hungarian notation for variables
❖ “Although names like pch look funny and are hard to pronounce, they are filled with information.”!
❖ “Conveying information is far more important in naming your variables than being able to stand up and read your code aloud during a program review.”
/* pointer to character pointer */char **ppch;
! ! 1993
Use Hungarian notation for variables
❖ “Although names like pch look funny and are hard to pronounce, they are filled with information.”!
❖ “Conveying information is far more important in naming your variables than being able to stand up and read your code aloud during a program review.”
/* pointer to character pointer */char **ppch;
Don’t use Hungarian notation for variables
❖ “In modern languages we have much richer type systems, and the compilers remember and enforce the types.”!
❖ “Nowadays HN and other forms of type encoding are simply impediments. They make it harder to read the code.”
/* pointer to character pointer */char **ppch;
2011
2009
! ! 1993
Don’t use Hungarian notation for variables
❖ “In modern languages we have much richer type systems, and the compilers remember and enforce the types.”!
❖ “Nowadays HN and other forms of type encoding are simply impediments. They make it harder to read the code.”
/* pointer to character pointer */char **ppch;
2009
2004
Don’t use Yoda Conditions
❖ “Putting constants and expressions on the left-hand side of comparisons works only when one of the operands is a constant or an expression.”!
❖ “If instead you use a compiler switch, the compiler would alert you to every possible assignment bug.”
2011
! ! 1993 if (10 == x) // if 10, x is!
Don’t use Yoda Conditions
❖ “Putting constants and expressions on the left-hand side of comparisons works only when one of the operands is a constant or an expression.”!
❖ “If instead you use a compiler switch, the compiler would alert you to every possible assignment bug.”
if (10 == x) // if 10, x is! ! ! 1993
2010
Enforce singletons
❖ “If you want to define a class that allows only one object to be instantiated, enforce this by hiding all the constructors of the class and providing a static routine to access the class’s single instance.”
public class MaxId { private MaxId() {} public static MaxId GetInstance() { /*..*/ }}
2004
! ! 1993
Enforce singletons
❖ “If you want to define a class that allows only one object to be instantiated, enforce this by hiding all the constructors of the class and providing a static routine to access the class’s single instance.”
public class MaxId { private MaxId() {} public static MaxId GetInstance() { /*..*/ }}
2004
2011
Prefer Write-Once Variables❖ “The more places a variable is
manipulated, the harder it is to reason about its current value”!
❖ “Variables that are a ‘permanent fixture’ are easier to think about”!
❖ “Immutables tend to more often be trouble-free”
2004
! ! 2000
2011
Prefer Write-Once Variables❖ “The more places a variable is
manipulated, the harder it is to reason about its current value”!
❖ “Variables that are a ‘permanent fixture’ are easier to think about”!
❖ “Immutables tend to more often be trouble-free”
2011
Design for Unit Tests❖ “Most important, is it possible
to automatically and thoroughly validate the design using a unit test? If not, you should consider using an alternative design that can be tested.” 2004
! ! 1993
Design for Unit Tests❖ “Most important, is it possible
to automatically and thoroughly validate the design using a unit test? If not, you should consider using an alternative design that can be tested.”
! ! 1993
Use Hungarian Notation for Variables
❖ Prefix indicates type or intended use!
❖ Widely used after use in Microsoft Windows C libraries!
❖ “Systems Hungarian” vs. “Apps Hungarian”
char ch; /* a plain old character */ bool f; /* flags that are always TRUE or FALSE */ char *pch; /* a character pointer */ char **ppch; /* pointer to a character pointer */ char *szName; /* a zero-terminated string */
Why?
❖ Makes wrong code ‘look wrong’ [Maguire93], [McConnell04]!
❖ Easy to decipher pointer expressions [McConnell04]!
❖ Semantic prefixes add information that compiler doesn’t know about [McConnell04]!
❖ Standardized prefixes encourage consistent naming [McConnell04]
Why not?
❖ Encoding adds burden of deciphering [Martin09]!
❖ Not necessary for types anymore [Martin09]!
❖ Functions and classes are shorter now [Martin09]!
❖ Inappropriate in object-oriented systems [Hunt00]!
❖ Code is read more than it’s written [Hunt00]
Specify interface or implementation in class name
❖ Denote in the class name when a class is an interface or when it implements an interface!
❖ Typically using prefix ‘I’ for Interface and/or postfix ‘Impl’ for Implementation
class IBloober { virtual void blob() = 0; }; !class BlooberImpl : public IBloober { virtual void blob(); };
Why not?
❖ Users shouldn’t (have to) know they’re dealing with an interface, better to encode the Impl [Martin09]!
❖ Names ending in Impl duplicate information [Freeman10]!
❖ Can indicate poorly named interface or design [Freeman10]
Align similar statements
❖ Can be used wherever similar statements are done on multiple lines!
❖ Purely aesthetic enhancement
customerPurchases = customerPurchases + CustomerSales(customerID); customerBill = customerBill + customerPurchases; totalCustomerBill = customerBill + PreviousBalance(customerID) + LateCharge(customerID); customerRating = Rating(customerID, totalCustomerBill);
Why?
❖ Alignment scheme shows statements belong together [McConnell93]!
❖ Neater listing and quicker scanning [McConnell93]!
❖ Column edges provide “visual handrails” [Boswell11]!
❖ Doesn’t take much work [Boswell11]
Why not?
❖ A headache to maintain as names change and lines are moved amongst different indentations [McConnell04]!
❖ Emphasizes wrong things [Martin09]!
❖ Keep lists short [Martin09]!
❖ Eliminated by automatic reformatting tools [Martin09]
Keep functions short
❖ Functions should be as small as possible
std::string renderPageWithSetupsAndTeardowns( PageData pageData, bool isSuite) { if (isTestPage(pageData)) includeSetupAndTeardownPages(pageData, isSuite); return pageData.getHtml(); }
Why?
❖ Easier to read and understand [Martin09]!
❖ Documentary value (descriptive name) [Martin09]!
❖ Easier to improve function when the code is by itself [Boswell11]
Why not?
❖ Routines of longer length are no more error prone than shorter routines, shorter may have more errors [McConnell93], [McConnell04]!
❖ Routines of longer length are cheaper to develop [McConnell93], [McConnell04]!
❖ Tiny functions can hurt readability [Boswell11]
Don’t Repeat Yourself (DRY)
❖ Every piece of code must have a single, unambiguous, authoritative representation within a system [Hunt00]
Why?❖ Change in one will make two implementations diverge
[Hunt00]!❖ Similar code in two routines implies an error in
decomposition [McConnell93]!
❖ Duplication is a missed opportunity for abstraction [Martin09]!
❖ Duplication can indicate bad design [Hunt00]!
❖ Copy/Paste programming is bad, remember Y2K [Hunt00]
Early Return❖ Use guard clauses to return early for exceptional or
error conditions!
❖ Return early from e.g. linear search when a result is found
public boolean Contains(String str, String substr) { if (str == null || substr == null) return false; if (substr.equals("")) return true; ... }
Why?
❖ Implementing without guard clauses unnatural [Boswell11]!
❖ Avoids deep nesting [McConnell04], [Boswell11]!
❖ Single exit point less important with shorter functions [Martin09]!
❖ Single exit point less important with modern languages [Boswell11]
Why not?
❖ Breaks Structured Programming single-entry, single exit ‘law’ [Martin09]!
❖ Harder to understand a routine when unaware of all return points [McConnell93]
Prefer Exceptions to Returning Error Codes
❖ Use exceptions to clarify the control flow
try { socket.read(name); process(name); socket.read(address); processAddress(address); socket.read(telNo) // etc, etc... } catch (IOException e) { Logger.log("Error reading individual: " + e.getMessage()); }
Why?
❖ Clarifies normal flow of control [Hunt00]!
❖ Avoids violation of command query separation [Martin09]!
❖ Don’t force user to handle error immediately [Martin09]!
❖ Signal errors in a way that cannot be ignored [McConnell04]
Why not?
❖ High-level program flow obscured by bubbling up [Boswell11]!
❖ Alternative error handling often more appropriate [McConnell04]!
❖ Exceptions should only be used in exceptional situations (that should never happen) [Hunt00]
Tell, Don’t Ask❖ Objects make their decisions based only on the
information they hold internally or that which came with the triggering message [Freeman10]
((EditSaveCustomizer) master.getModelisable() .getDockablePanel() .getCustomizer() .getSaveItem().setEnabled(Boolean.FALSE.booleanValue()); !// becomes... master.allowSavingOfCustomisations();
Why?
❖ Produces more flexible code, easier to swap objects [Freeman10]!
❖ Hides internal structure [Freeman10], [Martin09]!
❖ Avoids train wrecks [Freeman10], [Martin09]!
❖ Minimizes coupling [Hunt00]
Use Functional Programming Principles
❖ Use knowledge of functional programming in imperative languages!
❖ Work with immutable data and pure functions where possible
Why?
❖ Avoid side effects [Garson10]!
❖ Simpler to debug (source of change easy to find) [Garson10]!
❖ Avoid thread race conditions [Carmack12]!
❖ The more places a variable is manipulated, the harder it is to reason about its value [Boswell11]
Program in Terms of the Problem Domain
❖ Model types and behavior in terms of the programming problem rather than the computer science solution
if (portfolioIdsByTraderId.get(trader.getId()) .containsKey(portfolio.getId())) { ... } !// becomes ... if (trader.canView(portfolio)) { ... }
Why?
❖ Increases code readability and understanding [North10]!
❖ Can evolve code when domain model evolves [North10]!
❖ Work at a higher level of abstraction to allow focusing on solving domain problems [Hunt00]!
❖ Hides low-level details [McConnell93]!
❖ Separating solution and problem domain is part of the job of a good programmer [Martin09]
Prefer Message-Passing in Parallel Systems
❖ Use message passing instead of shared mutable variables!
❖ Use copies of data where necessary!
❖ If the language doesn’t provide it, use libraries or frameworks that do
Why?
❖ Shared mutable memory is at the root of problems related to concurrency: race conditions, deadlock, livelock [Winder10]!
❖ Proven way of handling concurrency [Winder10]!
❖ Avoiding synchronization makes up for overhead [Martin09]
Step Through your Code in a Debugger
❖ Actively step through all new or modified code to watch it execute!
❖ As you step through code, focus on data flow!
❖ Step through every code path
Why?
❖ Allows you to get a ‘feeling’ for the code and gain confidence [Maguire93]!
❖ You could create tests, but debugging is much faster [Maguire93]
Why not?
❖ Problems can be found more quickly and more accurately by thinking [McConnell04]!
❖ A bug should be reproducible with a single command [Hunt00]
Test-Friendly Development
❖ Design your code so that it’s easy to test!
❖ Avoid use of global state!
❖ Avoid coupling
Why?
❖ Easier to test [Everyone]!
❖ Test-friendly code leads naturally to well-organized code [Boswell11]!
❖ Decoupling leads to easier refactoring [Boswell11]!
❖ Classes with less state are simpler and easier to understand [Boswell11]!
❖ Designing to test promotes reusability [Hunt00]
Why not?
❖ Could sacrifice readability for the sake of enabling tests [Boswell11]!
❖ Testing by itself does not improve software quality [McConnell93], [McConnell04]
Test-Driven Development
❖ Write test cases before writing code!
❖ Follow Red-Green-Refactor cycle!
❖ Do not write code without having test cases
Why?
❖ Forces you to think about requirements and design of the code, finds problems earlier [McConnell04]!
❖ Tests will cover virtually all production code [Martin09]!
❖ Creates short feedback cycle in development [Freeman10]!
❖ Integrates refactoring in development [Freeman10]!
❖ Takes same amount of time as creating tests afterwards [McConnell04]
Why not?
❖ Can create false sense of security (should not be only form of testing) [McConnell04]!
❖ Just keeping testing in mind can help improve the code [Boswell11]!
❖ Testing can get in the way of product development [Boswell11]
Boy Scout Rule
❖ “Always leave the campground cleaner than you found it”!
❖ When you check in a module, make a small improvement, regardless of who the original author was
Why?
❖ The end of deterioration of software systems [Martin10]!
❖ Systems gradually get better [Martin10]!
❖ Promotes collective ownership [Martin10]!
❖ Don’t be afraid of the code [Lewis10]
Why not?
❖ What kind of motto is “If it ain’t broke, fix it anyway”? [Maguire93]!
❖ Programmers don’t treat improved code as if it were new code [Maguire93]!
❖ Your colleagues are not bozos, they might have had their reasons [Maguire93]!
❖ Change in itself is not a virtue [McConnell04]
Conclusion
“The word is about,!there's something evolving,!whatever may come,!the world keeps revolving!!They say the next big thing is here,!that the revolution's near,!but to me it seems quite clear!that it's all just a little bit of history repeating”!!—Alex Gifford by way of Shirley Bassey
Arjan van Leeuwen / @avl7771
Conclusion
❖ (Better) unit testing and test-driven development have an impact on other practices!
❖ Concurrency finally having an impact, comeback of functional language principles!
❖ Many practices that might seem recent were in fact known for a long time
References❖ [Boswell11] Dustin Boswell, Trevor Forcher, “The Art of Readable Code”. O’Reilly, 2011.!
❖ [Carmack12] John Carmack, “Functional Programming in C++”. http://www.altdevblogaday.com/2012/04/26/functional-programming-in-c/, 2012.!
❖ [Freeman10] Steve Freeman, Nat Pryce, “Growing Object-Oriented Software”. Addison-Wesley, 2010.!
❖ [Garson10] Edward Garson, “Apply Functional Programming Principles”, from “97 Things Every Programmer Should Know”. O’Reilly, 2010.!
❖ [Hunt00] Andrew Hunt, David Thomas, “The Pragmatic Programmer”. Addison-Wesley, 2000.!
❖ [Maguire93] Stephen A. Maguire, “Writing Solid Code”. Microsoft Press, 1993!
❖ [Martin09] Robert C. Martin, “Clean Code”. Prentice Hall, 2009.!
❖ [McConnell93] Steve McConnell, “Code Complete”. Microsoft Press, 1993!
❖ [McConnell04] Steve McConnell, “Code Complete 2”. Microsoft Press, 2004.!
❖ [North10] Dan North, “Code in the Language of the Domain”, from “97 Things Every Programmer Should Know”. O’Reilly, 2010.!
❖ [Winder10] Russel Winder, “Message Passing Leads to Better Scalability in Parallel Systems”, from “97 Things Every Programmer Should Know”. O’Reilly, 2010.