Software Craftsmanship


“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”

craft (n.)

  • an activity involving skill in making things by hand
  • skill used in deceiving others 😱

Software craftsmanship is an approach to software development that emphasizes the coding skills of the software developers. It is a response by software developers to the perceived ills of the mainstream software industry, including the prioritization of financial concerns over developer accountability.

How it all came to be

...the short version

In 1992, Jack W. Reeves' essay What Is Software Design? suggested that software development is more a craft than an engineering discipline.

In 1999, The Pragmatic Programmer was published. Its sub-title, From Journeyman to Master, suggested that programmers go through stages in their professional development akin to the medieval guild traditions of Europe.

In 2008, a number of aspiring software craftsmen met in Libertyville, Illinois, with the intent of establishing a set of principles for software craftsmanship. Manifesto for Software Craftsmanship came to life.

Agile Manifesto

Over the next two years software companies, rival software companies, took part in a Craftsmanship Swap.

By learning from each other we are going to make each other stronger. By making each other stronger, we are making our companies more competitive. In our conversations about this partnership we acknowledged that we do compete with each other, but more often we are in competition against people and companies that do not share our values.

Martin, Robert C. (2009). Clean Code: A Handbook of Agile Software Craftsmanship

In March, 2014, Software Craftsmanship : Professionalism Pragmatism Pride was published by Sandro Mancuso.

Proposing a very different mindset for developers and companies, a strong set of technical disciplines and practices, mostly based on Extreme Programming, and with a great alignment with Agile methodologies, Software Craftsmanship promises to take our industry to the next level, promoting professionalism, technical excellence, the death of the production line and factory workers attitude.

Agile Software Development

is an approach to software development under which requirements and solutions evolve through the collaborative effort of self-organizing and cross-functional teams and their customer(s)/end user(s).


values

  • Individuals and Interactions over processes and tools
  • Working Software over comprehensive documentation
  • Customer Collaboration over contract negotiation
  • Responding to Change over following a plan




principles

  • Customer satisfaction by early and continuous delivery of valuable software.
  • Welcome changing requirements, even in late development.
  • Deliver working software frequently (weeks rather than months)
  • Close, daily cooperation between business people and developers
  • Projects are built around motivated individuals, who should be trusted
  • Face-to-face conversation is the best form of communication (co-location)
  • Working software is the primary measure of progress
  • Sustainable development, able to maintain a constant pace
  • Continuous attention to technical excellence and good design
  • Simplicity—the art of maximizing the amount of work not done—is essential
  • Best architectures, requirements, and designs emerge from self-organizing teams
  • Regularly, the team reflects on how to become more effective, and adjusts accordingly

We are agile ❕

At a surface level, things are looking very good...

but underneath it...

  • daily failure...as in stand-up failure ;)
  • estimating in time, not in story points
  • converting a story point into a unit of time
  • definition of ready...if it is not ready, then the developer did not think it through
  • definition of done is ignored
  • story ping-pong between development and business (product owners)
  • bus factor = 1
  • skipping retrospective...multiple times in a row
  • code reviews have more to do with the grammatical correctness of the comments than with code and how to keep it clean
  • development testing becomes a second class citizen
  • abusing the fact that theory and praxis are not / cannot be always the same
  • delivering value becomes a cumbersome process
  • management breathing down your neck and "suggesting" to cut down on quality assurance work (tests, reviews etc.)
  • low or non-existing team cohesion
  • faux agile
  • people do not leave projects/jobs, they leave toxic project/work cultures
  • this does not mean that we seak for an impractical purist Agile adherence

how to improve

  • daily failure...as in stand-up failure ;)
  • estimating in time, not in story points
  • converting a story point into a unit of time
  • definition of ready...if it is not ready, then the developer did not think it through
  • definition of done is ignored
  • story ping-pong between development and business (product owners)
  • bus factor = 1
  • skipping retrospective...multiple times in a row
  • code reviews have more to do with the grammatical correctness of the comments than with code and how to keep it clean
  • development testing becomes a second class citizen
  • abusing the fact that theory and praxis are not / cannot be always the same
  • delivering value becomes a cumbersome process
  • management breathing down your neck and "suggesting" to cut down on quality assurance work (tests, reviews etc.)
  • low or non-existing team cohesion
  • faux agile
  • people do not leave projects/jobs, they leave toxic project/work cultures
  • this does not mean that we seek for an impractical purist Agile adherence
  • sane and useful daily
  • real story points estimation
  • let a SP be a SP and estimate better / SPs should have a meaning!
  • have a mature DOR then stick to it!

  • have a mature DOD then stick to it!
  • story accepted => increment
  • bus factor = team size (code reviews, documentation, transparency)
  • do not skip retro, how else could the team improve ?
  • it is called code review for a reason, u get me?!

  • write tests!
  • responsible and healty flexibility

  • make use of tools and processes that help delivering value faster
  • have the guts to say no!...do it politely though and keep being professional and respectful about yourself and your work
  • high team cohesion
  • agile
  • people may still leave, but they will have a feeling of sadeness while doing so
  • one step closer to a sane agile practice

we are agile ❕

clean code

“...is elegant and efficient. The logic should be straightforward..., the dependencies minimal...,error handling complete, performace close to optimal so as not to tempt people to make the code messy with unprincipled optimizations. Clean code does one thing well.” - Bjarne Stroustrup

clean code

“...is simple and direct. Clean code reads like well-written prose. Clean code never obscures the designer's intent but rather is full of crisp abstractions and straightforward lines of control.” - Grady Booch

clean code

“...can be read, and enhanced by a developer other than its original author. It has unit and acceptance tests. It has meaningful names. It provides one way rather than many ways of doing one thing. It has minimal dependencies...” - "Big" Dave Thomas

clean code

“...always looks like it was written by someone who cares. There is nothing obvious that you can do to make it better...code someone left for you - code left by someone who cares deaply about the craft.” - Michael Feathers

clean code

“You know you are working on clean code when each routine you read turns out to be pretty much what you expected. You can call it beautiful code when the code also makes it look like the language was made for the problem.” - Ward Cunningham

clean code

The Boy Scout Rule

“Leave the campground cleaner than when you found it.”

If we all checked-in our code a little cleaner than when we checked it out, the code simple could not rot.

Naming

the second hard thing in Computer Science



use intention-revealing names
😩

                    // what's the intention of this variable?
                    int dsc; // days since creation

                    // too general, it hides the real intention
                    List<Employee> employees = employees
                        .stream()
                        .filter(e -> e.getAge > 30)
                        .collect(Collectors.toList());
😊

                    int daysSinceCreation;
                    List<Employee> employeesOlderThanThirty = employees
                        .stream()
                        .filter(e -> e.getAge > 30)
                        .collect(Collectors.toList());


avoid disinformation
😩

                    // container profession encoded into the name
                    List<Employee> employeesList = new ArrayList<>();
                    Set<Employee> employeeSet = new HashSet<>();

                    // these names vary in small ways
                    List<Employee> getAllEmployeesWhoseFirstNameBeginWithAConstant() {
                        return Collections.emptyList();
                    }
                    List<Employee> getAllEmployeesWhoseLastNameBeginWithAConstant() {
                        return Collections.emptyList();
                    }
                


make meaningful distinctions
😩

                    void copyLetters(char c1[], char c2[]) {}

                    // what is exactly the difference between these?
                    new Employee();
                    new EmployeeData();
                    new EmployeeInfo();
                
😊

                    void copyLetters(char source[], char destination[]) {}
                


interfaces and implementations
😩

                    // encoding the interface in the profession name
                    IShape square1 = new Square();
                
😊

                    Shape square = new Square();

                    // better encode the implementation in the profession name
                    public class EmployeeDaoImpl implements EmployeeDao {}
                


name your classes and methods appropriately
😩

                    // a class name should not be a verb
                    new FindEmployeeAdress();
                    // do not use multiple words per concept
                    fetchEmployee();
                    retrieveEmployee();
                    getEmployee();
                
😊

                    // classes should have noun or noun phrase names
                    new Square();
                    new EmployeeAddress();
                    // methods should have verb or verb phrase names
                    boolean isOlderThanThirty() {return true;}
                


name your classes and methods appropriately
😊

                    // javabean standard
                    setName("John");
                    getName();
                    isInsured();
                    setInsured(false);

                    // consider the fluent notation -> next level: Builder Pattern
                    List<Employee> employees = new ArrayList<>(Arrays.asList(
                    // setFirstName -> firstName ...
                     new Employee().setFirstName("Vlad").setLastName("Flore").setAge(33),
                     new Employee().setFirstName("Jonh").setLastName("Doe").setAge(25),
                     new Employee().setFirstName("Jane").setLastName("Doe").setAge(30)
                    ));
                


solution domain names, problem domain names
😊

                    new EmployeeAddress();
                    new JuniorEmployee();
                    new AssociateEmployee();
                    new AdvancedEmployee();
                    new SeniorEmployee();
                    new PrincipalEmployee();
                    new SeniorPrincipalEmployee();

                    // Visitor Pattern
                    new EmployeeAccountVisitor();
                

Functions



first rule of functions is that they should be small
second rule of functions is that they should be smaller than that
😊

                    public List<Employee> findSeniorEmployees() {
                        // 20 lines max.
                    }
                


functions should not be large enough to hold nested structures
the indent level of a function should not be greater than one or two
😊

                    public void doJustOneThing(Data data) {
                        if (isValid(data)) {
                            doSomethingCleverWithThatData(data);
                        }
                    }

                    private boolean isValid(Data data) {
                        return true;
                    }

                    private void doSomethingCleverWithThatData(Data data) {
                        System.out.println("Doing something clever with data...");
                    }
                


do one thing!
functions should do one thing, they should do it well, they should do it only
one level of abstraction below the stated name of the function
😩  there was this thing called SRP...

                    findSeniorEmployeesAndCalculateAverageSeniority();
                
😊

                    // they can be better tested
                    findSeniorEmployees();
                    calculateAverageSeniority();
                


can you extract another function from it with a name that is not merely a restatement of its implementation?
we want the code to read like a top-down narative
we want every function to be followed by those at the next level of abstraction
😩  valid data, statement lambda

                List<Employee> findSeniorFemaleEmployees(List<Employee> employees){
                  if (CollectionUtils.isEmpty(employees)) {
                    throw new IllegalArgumentException("There are no employees");
                  }

                  return employees.stream().filter(employee -> {
                      return employee.getSeniorityLevel() == SENIOR &&
                             employee.getGender() == FEMALE;
                  }).collect(Collectors.toList());
                }
                


😩  expression lambda too long

                List<Employee> findSeniorFemaleEmployees(List<Employee> employees) {
                  Assert.notEmpty(employees, "There are no employees.");

                  return employees.stream()
                   .filter(employee -> employee.getSeniorityLevel() == SENIOR &&
                                       employee.getGender() == FEMALE)
                   .collect(Collectors.toList());
                }
                


😩  ok, improvement, but could be better

                List<Employee> findSeniorFemaleEmployees(List<Employee> employees) {
                    Assert.notEmpty(employees, "There are no employees.");

                    return employees.stream()
                        .filter(employee -> employee.getSeniorityLevel() == SENIOR)
                        .filter(employee -> employee.getGender() == FEMALE)
                        .collect(Collectors.toList());
                }
                


😊

                List<Employee> findSeniorFemaleEmployees(List<Employee> emps){
                    Assert.notEmpty(emps, "There are no employees.");

                    return emps
                            .stream()
                            .filter(this::isSenior)
                            .filter(this::isFemale)
                            .collect(Collectors.toList());
                }
                // minimal vertical distance
                public boolean isSenior(Employee employee) {
                    Assert.notNull(employee, "Employee does not exist.");
                    return employee.getSeniorityLevel() == SENIOR;
                }

                public boolean isFemale(Employee employee) {
                    Assert.notNull(employee, "Employee does not exist.");
                    return employee.getGender() == FEMALE;
                }
                


function arguments
😩

                    void method(String one, String two, Integer three, Boolean four,
                                boolean five, Data data, String... extra){
                        // does nothing,
                        // 25.06.2019 Vlad suggested I should read Clean Code! I won't!
                        // is usually buried deep down in the code
                        // forcing you to jump all over the place to find it
                    }
                


the ideal number of arguments for a function is zero
monadic (1), dyadic (2), triadic (3), polyadic (avoid!)
😊

                    // ask a question about the argument
                    boolean isOlderThanThirty(person)
                    // operate on the argument, transforming it and returning it
                    InputStream fileOpen("myfile.txt")
                    // flag arguments are ugly! SRP, remember?
                    void doSomething(boolean flag)
                    Point p = new Point(0,0) // the data belongs together
                    assertEquals(expected, actual) // use assertj for fluent assertions
                    assertThat(frodo.getName())
                        .startsWith("Fro")
                        .endsWith("do")
                        .isEqualToIgnoringCase("frodo");
                    assertEquals(message, expected, actual)
                


no side effects, side effects are lies
functions should either do something or answer something, not both
😩

                    boolean checkPassword(String userName, String password) {
                        // code to check the password
                        ...
                        initializeSession(); // remove this!
                        return true;
                    }
                
😊

                    if (checkPassword("Vlad", "1234")) {
                        initializeSession();
                    }
                

DRY

Do not Repeat Yourself

SRP

Single Responsibility Principle


POLA

Principle Of Least Astonishment

If a necessary feature has a high astonishment factor, it may be necessary to redesign the feature.

(Wikipedia)

KISS

Keep It Short and Simple

Do not just copy others code...

...and all the bugs in it

Comments



  • are at best a necessary evil
  • the proper use of them is to compensate the failure to express ourself in code
  • they lie!
  • truth can only be found in one place: the code
  • comments to not make up for bad code
  • mandated comments - plain silly
  • explain yourself in the code
  • good comments exist too, but we should try to live without them!
  • the only truly good comment is the comment you found a way not to write

                    /**
                     * The Employee class.
                     */
                    class Employee {
                     /**
                      * The gender of the employee
                      */
                     private Gender gender;

                     /**
                      * Get gender.
                      *
                      * @return the Gender
                      */
                     Gender getGender() {
                         return this.gender;
                     }
                    }
                

                    class Employee {
                        private Gender gender;

                        Gender getGender() {
                            return this.gender;
                        }
                    }
                

                // TODO replace with a method call on the object in question

                // Check to see if the employee is a senior female employee,
                // if so do ...
                if(employee.getSeniorityLevel() == SENIOR &&
                    employee.getGender() == FEMALE){}

                if(employee.isSeniorFemaleEmployee()){}
                


the worst of the worst

                    void doSomethingExtraordinary(){
                        // imagine extraordinary code here
                        // more extraordinary code
                        // even more extraordinary code
                        // I'm losing it...too much extraordinary code!
                        // this will make such a beautiful commit
                        // ...even if all the code is commented out!
                    }
                

Write Tests!

Write Tests!

T est D riven D evelopment



the three laws of TDD

“You may not write production code until you have written a failing unit test.”
“You may not write more of a unit test than is sufficient to fail, and not compiling is failing.”
“You may not write more production code than is sufficient to pass the currently failing test.”
✔ keep tests clean
✔ what makes a test clean?
readability  readability  readability
✔ what makes tests readable?
clarity  simplicity  density of expression
✔ one assert per test
✔ single concept per test
✔ the three As
Arrange   Act   Assert

but why do we write tests?

so we can complain about them? 

so we have more work to do, a.k.a more € to bill? 😏  

so we can refactor!  

so we can develop faster and deliver robust code  

Write Tests!

nota bene

  • TDD can help you increase the number of relevant tests
  • more relevant tests => robuster code
  • happy paths are not enough
  • code coverage is no the ultimate metric
  • write tests for the negative case too
  • try now and then to switch the actual and result arguments of an assert and see what happens
  • have a look at the Data Driven Testing
  • try something crazy - Mutation Testing

Write Tests

Fast

Independent

Repeatable

Self-Validating

Timely

F.I.R.S.T.

Write Tests!


                @Test
                public void addMethodShouldSumTheArguments() {
                    // Arrange
                    int firstArg = 7;
                    int secondArg = 7;

                    // Act
                    int result = calculator.add(firstArg, secondArg);

                    // Assert
                    assertThat(result).isEqualTo(14);
                }
            

Thank You!

and keep your code clean!

software craftsmanship on github