Clean Code

Notes about Clean Coding

Last Updated: August 12, 2020 by Pepe Sandoval

Want to show support?

If you find the information in this page useful and want to show your support, you can make a donation

Use PayPal

This will help me to create more stuff and fix the existent content... or probably your money will be used to buy beer

Clean code

My personal notes from Uncle Bob clean code lectures, they have been paraphrased or written in my own words just for my personal reference

Lesson 1: Intro to clean coding

  • We make a mess because we want to go fast
  • The only way go fast is to go well

  • Adding people doesn’t make things go fast because It is the old code that is actually training the new people

  • No one writes clean code at first attempt, once the code works it most likely be a mess, after that it is when you need to clean it

  • Having code that works is only half the job, it will take you half of your time, clean it will take the other half
  • You are not done when it works, you are done when it is right

  • Clean code does one thing well

  • You must write code that other people can maintain, make it work is not enough

  • It is more important that your peers understand the code your write than the computer understand the code you wrote

    • If I have code that doesn't work but I understand it someone probably will be able to make it work,
    • If I don’t understand the code but works perfectly as soon as we need to add extra functionality or the requirement changes the code is useless
  • Function names should be verbs because they do things

  • Every line of the function should be at the same level of abstraction

  • Code that goes up and down the abstraction levels is not clean code

  • Explanatory variable the purpose is to explain what its content is

  • clean code allows the reader to exit early

  • Function should be small. They should do one thing, one thing means you cannot meaningfully extract another function from it
  • If a function contains code and you can extract that code to create another function the original function is doing more than one thing

  • A set of functions that manipulate a set of variables is a class

  • A large function is usually just a class with a lot of smaller functions inside

  • Two or three arguments per function, if you already have 5-6 things to pass into a function, if they are so cohesive (related to each other) to be passed into a function together maybe they should be an object

  • Don’t pass Booleans to functions create separate function don’t use them as testing arguments

  • Polymorphism over switch/if-else statements

  • Open close principle: a module should be open for extension but closed for modification, you should be able to extend the behavior of a module without modifying that module, this is usually implemented with base classes and derivatives

  • Switch statements creates an dependency magnet

    • .jar files and DLLs are dynamic linking objects which means everything's is loaded and linked at runtime, they exists so you can independently deploy chunks/modules of your system
  • Dynamic linking objects exists so we can independently deploy aesthetics modules (like the GUI) from core functionality and data managers c(ode that interacts with a database code for example)

  • side effect refers to code (or function) that changes the state of the system,

    • Functions like open or new have side effects because they leave a file opened or leave a block of memory allocated, before calling the function the system had a state that was using certain amount of memory after calling the function it has a state in which it uses more memory
    • Side effect functions come in pairs (open/close, new/free, lock/unlock, etc.)
    • Contain side effect code so you don’t forget to close, free, unlock, etc.
    • Command and query separation: commands change state of the system (function that return nothing) and anything that returns value it won't change the state of the system
  • Use exceptions over return code (? not fully convinced of this, it needs examples to support argument)

    • If a function can throw an exception just put the call to that function inside a try-catch, don’t add more code before or in the try-catch
  • structured programming: every algorithm can be written out of 3 structures (sequence, selection if-else, iteration loops)

  • Science is a set of theories, conjectures, hypothesis that cannot be proven correct, experiments can prove them false but never true, we have not proven that airplanes can fly, we have just surround with so many tests, it doesn't matter we don’t prove it correct, we can do the same with software by surrounding with tests, we demonstrate our software is not incorrect

  • Why would we write code but don't write tests for every line of code? why dont we test the individual lines we write?

Lesson 2: Explicit Code & Comments

  • Purpose of a comment is to explain the code that cannot be explained by the code itself

  • Comments are to compensate for our failure to express ourselves in code, so every comment represents a failure and you will certainly fail, that's OK but consider the comment as a unfortunate necessity

  • Don't comment first, try everything else first, use a comment as last resort

  • Comments are a lot of times bad because we don't maintain them

  • Comment, don't make up for bad code, try to clean the code if you fail then write a comment

  • Explain yourself with code not with comments, make the code express itself

  • Never comment about code that is somewhere else

  • Name of a variable should be proportional to the size of the scope that contains it, long scopes need long names. This is the reason i, j or d are good a variable names if for example they are used on a short loop

  • Name of functions and classes should be inversely proportional to the size of the scope that contains it, the bigger the scope the smaller the name, the more specific the longer the name, while the more general the shorter

Lesson 3: Expectations in a SW project

The way we imprint professionalism into the SW development profession is by setting high expectations like:

Release good quality code

  • This means release code you know it works
  • Release code that has been tested at all levels (unit-integration-system)
  • Release code that is clean and understandable
  • Release code that is well organized

Always be ready to deploy

  • This doesn't mean it will have enough features but should be ready to be released and work (testing is done, documentation and all the work needed to make current features work is done)

Stable productivity

  • We should not get slower as time passes
  • We should be able to produce features at the same rate at the beginning middle or end of project
  • The reason we slow is because we make a mess

Inexpensive Adaptability

  • It must be cheap and easy to make changes to the system, because the main purpose of software is that must be changeable
  • in more realistic words: the cost of the change is proportional to the scope of the change
  • If doing a minimal change like changing a label on the system costs me a lot we are not fulfilling this expectation

Continuous improvement of the code

  • Code should get better with time, the design and the architecture should get better with time
  • Boy's scout rule: Check in the code a little better that you checked it out

Fearless Competence

  • This means don't be afraid of the code, don't be afraid to clean it, to improve it and restructure it
  • Avoid the "if ain't broke don't fix" mentality
  • If we choose change the code in a way to minimize the risk of breaking it instead of changing it to improve the general structure then the code will just rot and get worse with time, the productivity will slow to a point it is not possible to make a change without breaking something
  • To be fearless you need confidence to have confidence you need feedback mechanisms that run fast, are easy to run and tell you everything is OK, in other words tests

  • Fragile test problem: When test are so coupled to the system that if you change something in the system the whole test suite break

  • To avoid the fragile test problem we need to consider tests are part of the system so need to be designed as part of it with the same techniques to have a good design. They need to be decoupled from the system

  • The definition of bad design is a design that when you make a change in one part that change breaks a lot of things in another part

Extreme Quality / QA must find nothing

  • QA is not the tool to find bugs, they should find nothing
  • QA belongs at the beginning of the process
  • QA is the one that tells you how the system should work

We cover to each other

  • We cover for each other when a player goes down
  • To make sure someone can cover for you is your responsibility and to do this you need to walk someone through what you do
  • Pair enough (1 hour a day or a couple times a week) to be able to cover for each other

Honest estimates / Say "NO"

  • The most honest estimate: I don't know is the right but less useful answer
  • Define the shape of what we don't know
  • PERT (Project Evaluation In Real Time): You give 3 numbers best-case, nominal-case/usual-case and worst-case
  • A coder is hired because he knows how to code and works very hard to say "YES" but most importantly a coder knows when the answers is "NO" or "I don't know"

Lesson 4: SW Disciplines

  • Sequence, selection (branching) and iteration are the invariants (stuff that never change) in software, they are the fundamental blocks of software
  • Try to have if statements and while loops of only one line
  • The main SW development paradigms are 3: Structured, Functional and Object-oriented

Test Driven Development

  • Test Driven Development is a discipline that has the following rules which are followed in a endless loop:

    1. You are not allowed to create any production code until you have at least one test that fails because the production code doesn't exists
    2. You are not allowed to continue writing a test that you already know will fail and not compiling or running is a failure. In other words as soon as you realize the have written a test that fails you must stop written the test
    3. You are not allowed to write more production code than the one needed to make the previous test pass
    4. Every change you add to the test make the more specific or test more specific things while every change you add to production code makes it more general

      Disciplines are arbitrary or have arbitrary components and behaviors that are driven by a substantial motive. Arbitrary means in simple words it is just there because of a guess that has proven to work based on experience and it doesn't cause problems

  • The tests must be like code examples of how to use your system/app, they are short snippets of the code that explain how one part of the system works. E.x. if you want to create an object then there should be a test that does that and tells you what you have to do, so tests if implemented correctly will give you all the low-level detail of how the system works

  • A test suite that doesn't allow you to make a decision or give you enough confidence to know your code works is useless

  • Test Driven Development Examples:

    • General: You need to code a feature or create some code that does something, so you start writing a test, once you realize you can run it and it fails you stop and write code that make that test pass, once the test pass you continue writing other test for the thing you are creating until you are able to finish a test suite that proves that what you are creating works or until you realize the test will fail, in that case you go back to writing production code to try to make the test pass pass and repeat that pattern
    • Specific: I want a function to add two number so I write a line for a test, oh it doesn't compile because I don't have the function declared, I'll write some code to make that compile, now it compiles, now let me continue with the test to prove my code really adds two numbers, now it fails because I don't have the implementation of the function so I go back to implement that...
  • Inheritance is the tightest coupling we have so we want to have as little inheritance as possible or inherit as few implemented things as possible

  • Mutation testing: is an computationally expensive technique of testing in which a test suite that pass is run and the coverage is recorded then a mutation tool changes/mutates something in the code (change a == for a != for example`), then runs the test suite again, measures the coverage expecting one or more test failures, if a test pass it means a mutation is alive, these mutation are counted and reported since they point we have either a bad test, a bad code or bad coverage

Lesson 5: Architecture

  • Architecture must be flexible because as we implement it, the code and modules starts imposing pressure on the architecture which change the shape of the architecture and this change continues every time a new feature is added

  • The architecture is living thing, it changes with the system on a day to day basis

  • Good architecture results in good structured SW in which the effort to build it, to maintain it and the struggle to continue to add new stuff to it is minimized while the functionality and flexibility is maximized

  • The goal of SW architecture is to minimize the effort and resources required to build and maintain a software system

  • Quality of design can be measured by measuring the amount of effort it takes to meet the needs of a customer, if effort increases design quality decreases (it is bad) while if effort remains constant of shrink with time then design is good

  • We want to go fast and become overconfident we will be able to deal with the mess later, so the mess builds slowly but inevitably and eventually it is so big it dominates everything about the project

  • A discipline may feel slow even if they are actually speeding you up

  • SW has two values the value of what it does and the value of its structure but we tend to focus on what the SW has to do (satisfy the requirements) and if we do that we are maximizing the fact that it works today at the expense of making it flexible and being able to change so when the requirements change (and they will, they always change) the cost to change it will be enormous and if the cost is to big that will just kill the project

  • Programmers know we need to build a structures that supports the requirements, while stakeholders only know/see the requirements, programmers need to communicate the importance of structure

  • We (Programmers) tend be not bad at emotional conformation and relating emotionally to people

  • Behavior is urgent structure/architecture is important, because it has an extreme long term value while the requirements are urgent because they have a short term value

  • Focus on important and urgent, also on the important but not urgent

  • Architecture souls express the intent of the application

input-process-output models

  • The web and databases are just I/O devices from the SW developers point of view

  • Model View Controller (MVC): input-process-output model intended to be uses in small scale, you have an MVC for a button or a textbox, not a whole webpage

    • Model: know how to perform business rules operations, does data transformations
    • Controller: knows everything about inputs and restructures input into commands for the model
    • View: Knows how to display model data, present it
      • View registers a callback with the model, when model changes it tells the view 'hey redisplayed me because I changed'

MVC example

  • Object Oriented Use case driven: Interactor (object that represents a use case) guides/coordinates the execution of the entities (these represent the actual business rules, which are the things you do even if there is no computer, the manual steps, e.x. in a interest calculator app do the math operations to calculate the interest is a business rule you would do it by hand or with traditional calculator even if you don't have a computer) then there is data in and out of the system, the data movement of data is done through interfaces or boundary objects, interactor uses the input boundary objects to push data across the entities and the methods on the boundaries allow data to go in and out

  • Request model is just a raw data structure that holds the input data, doesn't know where it comes from (for example the web), it gets passed through the input boundary to the interactor, the interactor interprets the data and coordinates the entities, the entities perform operations or transform the data, then the interactor gathers the result data from the entities and builds a result model which is just raw output data that gets passes out through the output boundary

Object interactor-entities-boundary example

  • the presenter jobs is to reorganize the response model (raw data) to be ready to be displayed, response model holds objects (e.x. has a currency object, date object, etc) the presenter grabs those objects and transform them to be displayable (e.x to a string)
  • View model is a data structure that contains information about the data and data itself ready to be displayed (for or example strings properly formatted), if a button must be grayed the presented will set a Boolean in the view model that indicates this, the view just takes the data out of the view model and put it into the display

Presenter example

  • We don't want the things that perform business rules/operations coupled to database language like SQL, we need and entity gateway that has an interface (not implemented methods) for every query we want to perform, then we have the entity gateway implementation that has the knowledge on how a specific database works, if we have an SQL database all the SQL will be in the implementation

DB and entity gateway example

  • An object is something that contains behavior, not necessarily data, if it contains data most of the times user should not know about this

  • Object Relational Mapper (ORM) actually just fills data structures

  • A good architecture is a SW structure that allows you to defer critical decisions for as long as possible, defer them to the point where you've got enough information to actually make them or not make them at all, allows you to defer majors decisions until the last responsible moment

  • Make the GUI and database a plugin into the business rules

  • Using a framework comes with a great commitment to the framework

Lesson 6: Manage a SW project

  • The reason we manage is because mismanagement cost a lot (meaning, produce the wrong product, create a product of inferior quality, work 80 hours a week, etc)

  • Good (quality), Fast (Time to Market), Cheap (Cost effectiveness), Done... Pick only 3 the fourth you will not get it.

    • A good manager knows how good, how fast how cheap, what will be done to deliver the project to the best possible outcome (good enough) to do this we need data
  • Agile is a way to produce data to destroy hope, to know how messed up things really are

  • Dates are fixed because the obey business, for example it has to be done by November because we ran out of money in December

  • Requirements change all the time because they users/customers don't know what they want, the more we talk about what they want they kind of get a better idea of what they want but they really don't know until they see something execute that is not what they want so then they say "oh yeah that is not what I wanted"

  • we need to get things in front of customers very early so they can tells us "oh that's not what I wanted, I know that is what I said but now that I see it I realize it is not what I want at all", we need to start this feedback cycle fast

  • Waterfall model in which we analyze, then develop then implement doesn't work because once you start implementing you realize requirements were not clear and design won't really help solve the problems we want so the only solution is to do everything at the same time

    • We never know when design and analysis is done because we don't have a clear definition of done for these stages
  • On Agile we do exploration phase that involves doing analysis,design and implementation the first 3-4 sprints to be able to estimate how much we can get done in average in a sprint and extrapolate when we will actually be done, we will realize we wont be able to do everything by the end of date so we make adjustments

  • Doubling staff does not double throughput because old people slow down due to they need to help ramp up new people and expecting new people to get faster by an arbitrary date (in a month for example) is risky because you really don't know that, you don't have data to know that and even if you have data it is not reliable because varies from person to person

  • We must focus on never ever deliver something you stakeholder don't need

  • The problem with estimates is that we need to tell a moron (computer) how to do something

  • Agile is not to go fast is to get the bad news as early as possible

  • scrum is a business facing process, then there are the engineering practices and the team behaviors

  • The purpose of an iteration is to produce data and measure how much can get done, an iteration doesn't fail because even if no points are done that is actually a result with data

  • Write acceptance test that defines done for every story so a programmer knows he is done when an acceptance test for that story pass, so acceptance test must be done by midpoint of the sprint

  • Seniors developers should spend most of their time pairing with other people, mid level people should spend some time pairing with junior people

  • If we have story that we don't know how long it should take, we can define a spike, which is a story to try to figure out how long it should take you to finish the first story

  • You need to try as best and as hard as you can to follow clean code practices and rules but sometimes the rules cannot hold all the time so we violate the rules


Want to show support?

If you find the information in this page useful and want to show your support, you can make a donation

Use PayPal

This will help me to create more stuff and fix the existent content... or probably your money will be used to buy beer