“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”
craft (n.)
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.
...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.
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.
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).
At a surface level, things are looking very good...
but underneath it...
“...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
“...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
“...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
“...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
“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
“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.
the second hard thing in Computer Science
// 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());
// 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();
}
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[]) {}
// 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 {}
// 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;}
// 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)
));
new EmployeeAddress();
new JuniorEmployee();
new AssociateEmployee();
new AdvancedEmployee();
new SeniorEmployee();
new PrincipalEmployee();
new SeniorPrincipalEmployee();
// Visitor Pattern
new EmployeeAccountVisitor();
public List<Employee> findSeniorEmployees() {
// 20 lines max.
}
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...");
}
findSeniorEmployeesAndCalculateAverageSeniority();
😊
// they can be better tested
findSeniorEmployees();
calculateAverageSeniority();
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());
}
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());
}
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;
}
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
}
// 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)
boolean checkPassword(String userName, String password) {
// code to check the password
...
initializeSession(); // remove this!
return true;
}
😊
if (checkPassword("Vlad", "1234")) {
initializeSession();
}
If a necessary feature has a high astonishment factor, it may be necessary to redesign the feature.
(Wikipedia)
...and all the bugs in it
/**
* 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()){}
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!
}
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.”
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 ✔
nota bene
@Test
public void addMethodShouldSumTheArguments() {
// Arrange
int firstArg = 7;
int secondArg = 7;
// Act
int result = calculator.add(firstArg, secondArg);
// Assert
assertThat(result).isEqualTo(14);
}