Clean Code: Fundamentals, Episode 1
- Does Clean Code Matter? "Implementation Patterns" by Kent Beck
- The Company Killed By Code
- The Productivity Gap
- Brook's Law - "adding manpower to a late software project makes it later"
- The Big Redesign in the Sky
- Zeno's Paradoxes - Achilles and the Tortoise
- Code Rot
- Rigidity - Starrheit (tendency to resist change - forces us to make changes at different places to fix a single bug/add a single feature)
- Fragility - Zerbrechlichkeit (single bug/feature causes malfunction in other independent modules)
- Inseparability - Untrennbarkeit (can't separate modules for reuse)
- Opacity - Undurchsichtigkeit (when the code doesn't tell you what it does)
- What is Clean Code?
- Bjarne Stroustrup: "I like my code to be elegant and efficient - Clean Code should do one thing"
- Grady Booch: "Clean Code is simple and direct - Clean Code reads like well written prose."
- Michael Feathers: "Clean Code always looks like written by someone who cares."
- Ward Cunningham: "You know you are reading clean code, when every routine you read, is pretty much what you expected"
- The Boy Scout Rule: "Leave the world better than you found it"
- Astronomy: Period Luminosity Relationship by Henrietta Swan Leavitt
Clean Code: Names, Episode 2
- Reveal Your Intend
- Tim Ottinger's variable and class naming rule
- Describe The Problem (give meaningful names)
- Avoid Desinformation (e.g. given concrete name to an abstract class)
- Pronounceable Names (
skipped_tests instead of
- Avoid Encodings (don't code type into variable name, e.g.
bSkipped for boolean)
- Parts of Speech (boolean isEmpty, functions as verbs and classes as nouns)
- The Scope Length Rule
- the longer the scope of a variable the longer the name
- using abbreviations when used within ~3 lines
- public functions - shorter names; private functions longer names
Clean Code: Functions, Episode 3
- Functions should do one thing, they should do it well, and they should do it only
- The first rule about functions - functions shall be small
- about 4 to 10 lines, on older days a screen size (=24-4 lines)
- big functions can be converted into a class with well written names (see previous chapter)
- small functions act as sign posts
- The Geography Metaphor
- we find our way in big functions because the code looks like mountains
- Back in 1970s a function was measured in us
- Nowadays it takes 1ns or less
- We should optimize our code for readability
- The Bedroom Metaphor - when working in teams, we have to take care to have everything tidy (like grown-ups in their bedroom)
- Humens are good in recognizing landscape (nested blocks, long lines, etc)
- But only if you are familiar - not if you are new to the code
- Where do classes go to hide? In long functions
- Literate Programming by Donald Knuth -> used as an example for bad code
- Working effectively with legacy code by Michael C. Feathers -> Writing unit tests by capturing the output in file(s) and comparing the code against it. Then refactor the code.
- Convert function into a class with an
- break down
invoke() method into functions
- give class and
invoke() method proper names
- One Thing!
- if your function has many sections, it is not one thing
- it the function manipulates more than one level ob abstraction, it is doing not only one thing (Low Level/Implementation Details = StringBuffer, PageCrawler, etc. while High Level of Abstraction/Business Concepts = TestablePages, InheritedPages, etc)
- Extract Till You Drop! If you can extract another function from it, it is not doing one thing
- Brackets are an opportunity to extract
- Refactoring by Martin Fowler - using for an example to refactor
- Feature Envy
Clean Code: Function Structure, Episode 4
- Function Arguments
- Three Arguments as Maximum
- Builder Pattern
- No Boolean Arguments Ever
- No Output Arguments Please - Readers do not expect to have data coming out of a function into an argument - use return value instead
- The Null Defense - don't use null as value
- Defensive Programming - argument range checks only for public APIs, internal code (private methods?) shall be verified by unit tests instead
- Step-down Rule - important stuff goes at the top, details to the bottom (user can stop reading earlier)
- Switches & Cases
- use polymorphism instead of switch-case
- OO can invert the run-time dependency in source-code by interfaces
- The Fan-out Problem
- Switch statements make independent deployment more difficult
- Switch statements break the plug-in structure for our applications
- Paradigms - Functional Programming (1957 - Lisp)
- no local variables - passing variables to function, no iteration - recursion
- functions are state-less - same input has always same output - there are no side effects
- Side Effects - Temporal Coupling
- calling a method expects that another method is called before (
- Solution: Passing a block (callback function) - e.g. open a file, processing the block on the file and closing the file, all in one function
- Side Effects - we don't want to eliminate side-effects, we want to get discipline with side-effects
- Side Effects - Command / Query Separation
- Commands: have side effects (alter the object the work on)
- Commands: return
- Commands: throw an exception when something goes wrong (login failed)
- Queries: not have have side effects (e.g.
- Queries: return a value
- Side Effects - Tell instead of ask!
- Tell objects what do to, instead of asking for their state and make decisions based on this state (move code to its data (class))
- Reduces the number of query functions
- train wrecks - o.getX().getY().getZ().doSomething()
- The Law of Demeter - you are not allowed to perform methods on objects returned by functions (not passed by arguments, not created locally, not instance/global variables)
- code which tells-not-asks is decoupled from its surroundings
- Paradigms - Structured Programming (1967 - Edgar Dijkstra - GOTO considered harmful (wiki))
- younger than 'Object-oriented Paradigm' (1962-66)
- If a system is build of sequence, selection and iteration, you can construct proofs of correctness: A provable system is a understandable system.
- All blocks, modules, systems share: a single entrance at the top and a single exit at the bottom
- Early Returns - avoid mid-loop breaks and returns
- Error Handling
- Michael Feathers: "Error handling is important - but when it obscures logic, it' wrong
- Errors First - write first the error-handling code before the rest of the code
- Prefer Exceptions (over error codes in return value)
- Exceptions are for Callers
- Use Unchecked Exceptions (checked exception create a reversed dependency: from the implementation to the caller - violates independent deployability and breaks the open-close-principle)
- Null Object Pattern
- Special Cases - create classes for special cases in factory method
- Null is not an Error - null could traverse through the code and cause a NullPointerException somewhere...
- Null is a Value - e.g. -1 could be used in a computation, better return null if you mean 'nothing'
- Trying is one Thing -
try must be the first word in a function (after variable declarations), the body of the
try block must contain a single line (a function call) and the
finally blocks are the very last thing in the function - nothing follows them.
- A function should do one thing - and error handling is one thing
Clean Code: Form, Episode 5
- The code should be the coding standard
- Comments should be rare
- When comments become common they become worthless
- Comments should be reserved for that cases where the programmers attention is necessary
- Comments are failures
- Code should be written in a way that it doesn't need comments - thus every comment is a failure
- Comments are necessary in assembler, Pascal, Fortran and sometimes C
- Comments are lies
- Comments tend to be clutter and lies
- Comments rod because they tend to be non-local (changes in code could invalidate comments at some other place)
- Good Comments
- legal comments (copyright, etc)
- informative comments (e.g. describing a horrible regular expression)
- clarifications or explanations of intend
- warning of consequences
- todo comments
- public API documentation
- Bad Comments - if you see a wrong/bad comment: fix it or delete it
- Mumbling (your life situation, etc)
- Redundant Explanations (things which are obvious in the code, DRY)
- Mandated Redundancy (@title the title)
- Journal Comments (use source versioning system instead!)
- Big Banner Comments (e.g. position markers)
- Closing Bracket Comments (job done by IDE)
- Attributions ("added by Rick")
- HTML in Comments (comments shall be most readable in the code)
- Non-local commentation ("defaults to 8080", rods quickly)
- Commented-out Code
- Explanatory Structures
- instead of comments try to use explanatory variables and names (well written prose)
- e.g. move asserts to nice little helper functions ('reversed()', 'isPalindrome()', ...)
- whitespace carries information
- the first people see of your code is the formatting - we want to impress them
- formatting is about communication - communication is the first order of business of every programmer
- File Size
- project size and file size aren't related to each other
- FitNesse shows that significant systems and be composed of files that average of 50-60 lines (max. 500 lines)
- Large files contains stuff that hasn't been organized properly
- Smaller is Better
- Vertical Formatting
- Separate methods by one blank line
- Inside a function, use blank lines to separate variable declarations from the test of the executable code, separating if-statements and while-loops
- the vertical distance is a measure how related things are to each other
- Horizontal Formatting
- horizontal scroll bars are daemon spawn - manage your line length so that you never have to scroll to the right
- we like lines which are 30-40 characters in length
- we don't like lines which are over 80 characters
- everyone in the team should use the exact same style
- Uncle bob does not vote for a style, but suggests 2 spaces, Kernighan & Ritchie style
- a code which comes out of a team should look like the team has wrote it
- don't use reformatting (on checkout/checkin) - choose a style and then use it
- if you reformat the code (e.g. by the IDE) - make it in small snippets (otherwise merges could be a nightmare)
- a class exposes only public methods - and does not expose a state (variables)
- cohesive methods (manipulate many variables)
- minimize setters/getters - maximize cohesive methods
- if classes must expose data, they do so in the maximum abstract form possible ('percentage fuel' instead of 'gallons of gas')
- Data Structures
- objects protect you from new types - polymorphism protects your client code from new types in the server code (client works on car, while server creates electric or diesel car)
- classes break independent deployability when you add a method to your base class
- data structures expose new types
- 'data structures and switch statements' do not break independent deployability when adding a method (since it's only implemented in the utility class)
- The Expression Problem: is there any way to get protection from both (new methods and new types)?
- We use classes and objects when it is types, which are more likely to be added
- We use data structures and switch statements when it's methods more likely to be added
- Main vs. the application
- Views vs. models
- Database vs. domain objects
- on each boundary one side is concrete (e.g. domain objects, main) and one side is abstract (e.g. database, application)
- DB Interface Layer (e.g. for SQL Code) - should depend on the application and the database (and not vice versa! see 'invert source code dependencies' without inverting the flow of control)
- database table are so concrete that they never have a chance to be polymorphic
- The Impedance Mismatch between relational databases and object-oriented programming
- Object-relational mappers (like Hibernate) - map a 'data-base row' to a 'data-structure' (in memory)
- Methods of Domain Objects are business rules - so domain objects do not look like classes of the database schema
- Databases are designed for the enterprise (tuned for security and performance) - not for a particular appliation
- Filling data into business objects (with hidden data and real methods) which fit better to the application (than rows from the enterprise database)
- Instead of manipulating table rows we maniuplate business objects
- Example 1: Application - Interface Layer - Database
- The database layer converts back and forth (table row vs. business objects)
- Application side: bunch of interfaces wich declare data-access methods (table sorting, etc). The interface layer will implement the data-access methods by accessing data-objects from the database. We can use a ORM tool (like hibernate) too fetch the data-objects out of the database
- Example 2: View - Application
- View is concrete, application provides the interfaces (the view should know about the application, the application should know nothing about the views)
- Every time you write a comment, you failed
- Keep comments rare - work hard to find a way not to write comments
- Comments should not be mandatory
- No commented out code
- Whitespace discipline is important - be consistent
- Horizontal scrollbars will not be tolerated - line width should average at about 40chars and never exceed 120 chars
- The average file size should be less than 100 lines and never exceed 500 lines
- Classes are bags of functions that hide the data they manipulate - use tell don't ask (when setters are necessary, try to hide the implementation by abstracting it)
- Use classes to protect you from new types - not new functions
- Don't use business rules into data structures
- Use data structures to protect you from new functions - not new types
- Boundaries separate things that are concrete from things that are abstract - all source code dependencies cross these boundaries, pointing from the concrete stuff towards the abstract stuff
- Database tables and domain objects are not the same thing and even not strongly related
- Separate the domain objects from the database by putting a layer in between
Clean Code: TDD, Episode 6 (Part 1)
- I finish ur tests latr
- Fear and Code Rot
- Fixing code rot by cleaning the code
- With cleaning code we run into danger to break the code
- We can't clean code until we eliminate the fear of change
- Eliminating Fear
- Get a suite of tests, so no bug can escape it, which can be execute in a matter of minutes, any programmer can execute these tests, and which never can get out of date with the system.
- A long bug list comes from irresponsibleness and carelessness. Having a long list of defects can only mean that the development team is behaving unprofessionally.
- Refactoring Demonstration
- The Three Laws of TDD
- You are not allowed to write any production code until you have first written a failing unit test
- You are not allowed to write more of a unit test than it is sufficient to fail - and not compiling is failing
- You are not allowed to write more production code than it's sufficient to pass the currently failing test
- Cycles will be about 20 seconds long - writing test code to fail and write production code to make it pass
- Debugging Time
- Debugging is easy if the code was working a minute ago
- High debugging skills must correlate with low productivity
- Design Documents
- Unit tests are the code examples for the whole system (there is a unit test which creates an object every way the object can be created)
- Tests are a low level design document: they are written in a language you understand, they are unambiguous, they are so formal that they execute and they can't get out of sync with the application code
- If you write the tests first, you have to design the production code to be accessible from the production code
- Writing tests first makes production code testable == decoupled
- Courage to Change
- If I give you perfectly designed system but no tests - you will be afraid to improve and clean it - and over the time it will rot.
- On the other hand, if I give you a terribly design system but a comprehensive designed suite of tests, you will not be afraid to improve it and so over time it will get better and better
- If you want to flexible system - get a suite of tests that you trust
- You must trust your tests like your parachute when jumping out a airplane.
- If every production code was written to make a failing unit test pass, you will trust your test suite.
- If you write your tests after the production code, you will always be worried that it has got holes in it.
- Writing tests after feels like 'make work' since you know that your code is working - you tested it manually. This means you are taking shortcuts with your parashute.
Source: clean-code-episode-6 (part 1)
- cleaning means refactoring --> leads to refactoring tests --> tests are 10x bigger than the code --> refactoring needs 11x more time
- Tests runtime of seconds - quite limited tests without system interaction? Where to make the boundaries?
- Unit Tests (testing all methods/classes) vs Acceptance Tests (testing features)
- Mock-Objects!!! Interfaces
- Google: Change in the CMS determine which tests are executed
- Nothing makes a system more flexible than a suite of tests - because this suites of tests eliminates fear
Clean Code: TDD, Episode 6 (Part 2)
Source: clean-code-episode-6 (part 2)
- Red, Green, Refactor - The Bowling Game:
- Starting with design session: class Game, class Frame, class Roll, class TenthFrame
- The design gives us the vocabulary of the system, the basic components
- TDD: Design is used as guideline (not 100% following it)
- Red phase: what test do I have to write so that it forces me to write the code I want to? Write the simplest real code you can... It's ok to create duplicate code here!
- Green phase: making it work...
- Blue phase: refactor duplicate code (setup method), deleting empty/obsolete tests (tests which are covered by other tests)
- if the code asks you do to something horrible there must be something wrong with the design
- design smell: misplaced responsibility (The principle of least surprise comes into play here. Code should be placed where a reader would naturally expect it to be)
- Treat tests as it were production code (do refactoring, take the code serious)
- Answering the Objections:
- By following test driven development you write much more code than normal - therefor test driven development must slow you down: not true in the long term
- There are managers which will not allow you to practice TDD: programmers behaving as professionals
- Refactoring is rework - do it right the first time: every creative effort on the planet is done iteratively
- Who tests the tests? The tests test the production code, but the production code tests the tests
- One single change of production code can break hundreds of tests - maintaining all of those tests is simply to expensive: design your tests well - refactor your tests - treat your tests the way you treat your production code. Maybe your tests are too coupled with the production code? (add abstractions or other decoupling in your tests)
- Tests can prove the presence of bugs but not the absence of bugs: our goal is to create a parachute to eliminate the fear of change
- TDD encourages people to follow rules instead of using their minds and thinking: we don't want to make the same decisions over and over again
- It's the tests that matter, not when we write them: if you write tests first, you trust those tests - if tests are critical, the tests must come first
- How to handle legacy code? (1) "Working effectively with Legacy Code (Michael C Feathers)" -< Find some small part of the legacy code that you can test without making big changes, then use those tests to extend the design changes more safely.
- How to handle legacy code? (2) Don't do refactoring projects - instead when adding a new feature add additional tests in this affected area.
- How to test graphical user interfaces? Do not test the graphical representation - but the decision behind them (e.g. the boolean, whether a button is disabled or not). The content of the screen can be tested.
- How do you test databases? Don't test the database - mock it away and test the remaining code.
- Programmers are no testers. They need to grow up
- Discipline and Professionalism
- TDD is double entry bookkeeping - everything is set twice: one of the production code side and one on the test side
- QA should find nothing - and if, the developers should take steps to ensure that this kind of defect does not come again: "You don't ship code you don't know that it works"
- 100% code coverage - what's the sense to cover less than 100%? 100% is the goal - even if you cannot reach it.
- Doctors wash your hands - alpha geeks are reluctant to change their ways
Episode 8: SOLID Principles
- Architecture is the shape the system adopts in order to meet its use-cases. Use-cases shall be visible at the highest level of the system.
- Model-view-controller may be an excellent architecture for UIs - its not a good application architecture. Should not be visible at the highest level.
- User-interfaces, databases and frameworks are details to be hidden, they are not the central abstractions of our architecture. We should think of them as plugins - which can be quickly and easily changed.
- We can create boundaries which separate the application from external details, like the DB, UI, etc. We manage the source code dependencies crossing these boundaries, so that they cross in a single direction pointing towards the application.
- The Source Code is the Design
- "What is Software Design?" by Jack W. Reeves, 1992
- What do Engineers produce? Engineers produce documents that specify how to build products (architects produce blueprints, building diagrams; electronics engineers produce circuit diagrams; ...) In software engineering, the only document who fully specify the product is the source code.
- The running program/binary executable is the true product - not the source code. The source code is a document from which the true product (the running program) derives. Building means compiling.
- Other documents (e.g. UML diagrams) are preliminary - they are not the design.
- When building a house - the cost of designing is far less expensive than building it. Changing a house is quite expensive as well. Thus we spend a lot time for the design.
- In software, building the product is cheap (== compiling). But the cost of designing is high.
- If you evolve the design of a system, there is no guarantee that your design gets well.
- Design Smells
- Rigidity, Fragility, Immobility, Viscosity, Needless Complexity
- Rigidity (Starrheit)
- Is the tendency of a system to resist change.
- A system is hard to change, if the cost of making a change is high.
- If the smallest change forces a complete rebuild and test (e.g. taking 3 hours) the system is rigid.
- Reducing the build and test time (restructure) makes a system much less rigid and much easier to change.
- If a test needs long time to run, its a good indication that the developers are careless.
- Long build-times are a function of coupling (in C++ the build time is the number of coupled modules squared).
- Fragility (Zerbrechlichkeit)
- A system is fragile when a tiny change on one module causes others unrelated modules to misbehave.
- Solution: Manage the dependencies of modules and isolate them from each other.
- Immobility (Unbeweglichkeit)
- A system is immobile when its internal components cannot be easily extracted an reused in new environments.
- Immobility is caused by couplings and dependencies in the modules of the system.
- Use patterns which decouple the central abstraction of the application, from the DB, user-interface, etc.
- Viscosity (Zähigkeit)
- A system is viscose when necessary operations like building and testing are difficult to perform and take a long time to execute. (e.g. when check-ins, check-outs and merges take long time, the system is viscose because the cost of such central operations is high)
- Viscose system: even the simplest change is costly to make.
- The cause is always the same: irresponsible tolerance (developers tolerance conditions which they know to be bad)
- Cause it coupling: tight couplings make system hard to build, hard to test and hard to change.
- Solution: Decouple modules and manage dependencies that remain
- Needless Complexity
- Keep your software focused on the current suite of requirements - trust on your test and refactor/implement future requirements in the future.
- Dependency Inversion:
- Bad architecture: High level policy (core) depends on low level details (infrastructure) - direction: core -> infrastructure; Dependency in the same direction as the flow of control; Fan-out grows with each change
- Better architecture: High level policy depends on file abstraction (open, close, read, write);
- What is OO?
- Protecting high level policies from low level details
- OO design is all about dependency management
- SOLID Principlies are about Dependency Management:
- The Single Responsibility Principle
- The Open Close Principle
- The Liskov Substitution Principle
- The Interface Segregation Principle
- The Dependency Inversion Principle
- Principles of Component Cohesion describe the forces to cause classes to be grouped into independently deployable components:
- The Release-Reuse Equivalency Principle
- The Common Closure Principle
- The Common Reuse Principle
- Component Coupling Principles describe the forces that govern the dependencies between components:
- The Acyclic Dependencies Principle
- The Stable Dependencies Principle
- The Stable Abstractions Principle
Episode 9: The Single Responsibility Principle
- It's About Users:
- Responsibility to users who will request a change
- It's About Roles:
- Responsibilities are tied to actors
- The Two Values of Software:
- primary (when software is easy to change) and secondary (behavior - when it does what the user needs) value
- Unit tests vs. integration tests vs. system tests vs. ..
- CM Collision:
- Fan Out:
- class Employee depends on many classes (Database API, String API, Calculation API)
- Many classes depend on Employee - and thus will sensitive to those fanned out dependencies
- Co-location is Coupling:
- All classes have to be recompiled, even if they are not affected (user of calculation are force to be recompiled due to a change in the database)
- Encroaching Fragility:
- To avoid Fragility --> dupplicate your code???
- SRP - Single Responsibility Principle:
- A module has only one reason to change - only one responsibility
- we gather things which change for the same reasons - and separate things which change for different reasons
- Examples: assembly and place direction, arrow though chambers (terminate), drawing wator (draw chell with state), logging responsibility (Executive vs LoggingExecutive)
- Class and base-class are very tight coupled - and not separated
- Extract Classes:
- Interface Segregation:
- Welcome to Engineering:
- Case Study:
- Master Mind
- Game Designer - responsible for the messages (text, formatting, language, console, web, ..)
- Stagegiest - chooses the algorithm
- Customer - Responsible for the flow, decides guesses, advertisements, level-ups, badges,
- Customer is the architectual center
- Application is in the center and the other responsibilities plug into it
- Faking it:
- Unit tests tend to align with actors
- First only acters were identified
- Best time to draw system diagrams and documentation: when you are done
Episode 11: The Liskov Substitution Principle (Part 1)
Source: clean-code-episode-11 (part 1)
- Type Theory / What is a type?
- Does the set of all sets, that don't contain them selfs, contain them selfs? or If your mother only cooks for those, who do not cook for themselfs, who cooks for your mother? or This statement is false
- A type is a bag of operations (we do not care if a integer is implemented as two's complement, as long as addition, substraction, ... works)
- A class is a bunch of operations, and the data behind is private
- Types and Subtypes (e.g. derived classes)
- Liskov & Subtypes:
- If there are users
U, which use objects of type
T and there is a subtype of
S, the users could use objects of type
S instead (without knowing it).
- Duck Typing:
- Duck Typing: Defining a type by adding the necessary methods to a class (dynamic typing)
- Alternativ to static typing as in Java or C++
- Sending messages instead of invoking methods
- During runtime, it shows if the message can be accepted by the receiver - otherwise there will be a runtime error
- Duck Typing - when something looks like a duck, walks like a duck, swims like a duck, ... we call it a duck
- A type is just decribed by its features (=methods) not by its type
- Refused Bequest:
- Refused Bequest: Calling non-existend messages in dynamically types languages (ruby)
- Refused Bequest: Subtypes cause side effects/throw exceptions which the users of the base-class do not expect
- Example for 'The Representative Rule':
- Example where a square extends a rectangle - but now inherits
- Refused Bequest!:
- Side effects will result in undefined behavior
- Undefined behavior will crash the problem at the customer, while it works fine in your development environment
- It will take you weeks to find a problem, which occurs only once a day.
- Latent Open-Closed-Principle Violation:
- Solving side-effects by
if (rect instanceof square) .. (creating dependencies towards subtypes)
- Every refused bequest or violation of the Liskov substitution princibple is a latent OCP violation
- When the OCP is violated, the software gets rigid and fragile
- A square is a rectangle, but a piece of code representing a square is not a piece of code representing a rectangle
- Treat both as completly independent types
- The Representative Rule:
- Representatives (code) do not share their relationships (square, rectangle)
- Example: Lawers representing a couple getting divorced, will most unlikely getting divorced themselfs.
- Example Numbers:
Integer number "is a"
Real number, a
Real number is a
Integer does not extend (derive) from
- Example Lists:
- If S extens T, List<S> cannot be passed into where List<T> is expected.