You're still shipping bugs in 2020? 😱
Slides : https://bit.ly/2zDQlXR
@JoGrenat
Morning chit chat
and coffee...
You will never believe what the new guy did!
Don't you have code review?
Yes, but...
Maybe there's a deeper problem...
The new guy is always a pain in the a**!
Except sometimes it's me...
Jordane Grenat
@JoGrenat
And sometimes it's you
account.sendTransaction("John Doe", Date.now(), 100);
account.sendTransaction(100, Date.now(), "John Doe");
¯\_(ツ)_/¯
account.sendTransaction();
What needs
special attention
will eventually fail
What needs
special attention
reveals a flaw in conception
Production
IDE
Compilation
Where do we find bugs?
Code review
CI/CD
Compilation Error == Impossible Error
fetchUser(userId).then(user => {
console.log(user.name);
});
fetchUser(userId).then(user => {
if(user && user.name) {
console.log(user.name);
}
});
function fetchUser(userId: string): Promise<User | null> {
// ...
}
with strictNullChecks
fetchUser(userId).then(user => {
console.log(user.name); // Compilation Error!
});
export class Account {
sendTransaction(recipient: string,
amount: number,
date: number): string {
// ...
};
}
account.sendTransaction("John Doe", Date.now(), 100);
account.sendTransaction(100, Date.now(), "John Doe");
account.sendTransaction();
export class Account {
sendTransaction(recipient: string,
amount: number,
date: Date): string {
// ...
};
}
account.sendTransaction("John Doe", Date.now(), 100);
account.sendTransaction(100, Date.now(), "John Doe");
account.sendTransaction();
private void sendEmail(String email,
String subject,
String content) {
// ...
}
sendEmail("john@doe.com", "Hello!", content);
sendEmail("Hello!", content, "john@doe.com");
Primitive Obsession
void sendEmail(String, String, String);
void sendEmail(Email, Subject, Content);
Primitive Obsession
private void sendEmail(Email email,
Subject subject,
Content content {
// ...
}
sendEmail(new Email("john@doe.com"),
new Subject("Hello!"),
new Content(content));
sendEmail(null, null, null);
Primitive Obsession
private void sendEmail(@NotNull Email email,
@NotNull Subject subject,
@NotNull Content content {
// ...
}
Primitive Obsession
Only checked by the IDE in Java
sendEmail(new Email("wrongEmail.com"),
new Subject("Hello!"),
new Content(content));
private void sendEmail(Email email,
Subject subject,
Content content) {
if(email.isValid()) {
// ...
}
}
sendEmail(new Email("wrongEmail.com"),
new Subject("Hello!"),
new Content(content));
class Email {
public Email(String emailString) {
if (!isValid(emailString) {
throw new IllegalArgumentException();
}
// ...
}
}
An Email argument is
always a valid email!
sendEmail(new Email("wrongEmail.com"),
new Subject("Hello!"),
new Content(content));
Still catched at runtime...
class Email {
public Email(String emailString)
throws InvalidEmailException {
// ...
}
}
class InvalidEmailException extends Exception {}
Better
try {
Email email = new Email("wrongEmail.com");
} catch (InvalidEmailException e) {
// Fallback
}
class Email {
private Email(String emailString) {}
static Either<Reason, Email> fromString(String email) {}
}
Even Better
Either<Reason, Email>
Reason
Left
Right
Either<Reason, Email> email = Email.fromString(wrongEmail);
email.map(email -> sendEmail(email, subject, content));
class Email {
static Either<Reason, Email> fromString(String email) {
if (!isValid(email) {
return Either.left(BAD_EMAIL);
}
return Either.right(new Email(email));
}
}
Input filtering
Type cardinality
boolean
true
false
number
-23.4
-3
5489
π
2345.45
0
...
string
""
"😱"
"@&34"
"Œ"
"Hello"
"Goodbye"
...
Card(bool) == 2
Card(number) == ∞
Card(string) == ∞
Type cardinality
Boolean
true
false
Number
-23.4
-3
5489
π
2345.45
0
...
String
""
"😱"
"@&34"
"Œ"
"Hello"
"Goodbye"
...
null
null
null
Type cardinality
boolean
true
false
number
-23.4
-3
5489
π
2345.45
0
...
string
""
"😱"
"@&34"
"Œ"
"Hello"
"Goodbye"
...
null
null
null
undefined
undefined
undefined
function setDieValue(value: number) {
// ...
}
∞
function setDieValue(value: DiceValue) {
// ...
}
enum DiceValue {
ONE, TWO, THREE, FOUR, FIVE, SIX
}
6
if (value < 1 || value > 6) {}
Card(modelisation) == Card(business)
Modelisation
Business
- Pending
- Received
- Cashed
- Pending
- Validated
5
Study Case 1: Payment
class Payment {
method: string;
status: string;
}
Card(Payment) = Card(string) * Card(string)
= ∞ * ∞ = ∞
class Payment {
method: Method;
status: Status;
}
Card(Payment) = Card(Method) * Card(Status)
= 2 * 4 = 8
(Card, Received)
(Card, Cashed)
(Cash, Validated)
Study Case 1: Payment
("cb", "unknown")
enum Method { Card, Cash }
enum Status { Pending, Received, Cashed, Validated }
Study Case 1: Payment
class Payment {
private method: Method;
private status: CardState | CashState;
private constructor(m: Method, s: CardState | CashState) {};
}
Card(Payment) = 1 * Card(CardStatus) + 1 * Card(CashStatus)
= 1 * 2 + 1 * 3 = 5
static cardPayment(state: CardState): Payment {}
static cashPayment(state: CashState): Payment {}
Study Case 1: Payment
enum Payment {
Card(CardState),
Cash(CashState),
}
enum Payment {
Card(CardState),
Cash(CashState),
Free,
}
Study Case 2:
Natural Numbers
enum NaturalNumber {
Zero,
Succ(NaturalNumber),
}
Succ(Succ(Succ(Succ(Zero))))
4
Study Case 3: Random Die
type DieValue =
One | Two | Three | Four | Five | Six
Random.uniform [One, Two, Three, Four, Five, Six]
Random.uniform []
???
Random.uniform One [Two, Three, Four, Five, Six]
Not empty list = an element + a list
hand = Ace :: King :: Queen :: Jack :: Ten :: Nil
fifthCard = index 4 hand
sixthCard = index 5 hand
hand : Vect 5 Card
hand = Ace :: King :: Queen :: Jack :: Ten :: Nil
fifthCard = index 4 hand
sixthCard = index 5 hand
hand : Vect 5 Card
hand = Ace :: King :: Queen :: Jack :: Ten :: Nil
fifthCard = index 4 hand
sixthCard = index 5 hand
index : Fin n -> Vect n e -> e
hand = Ace :: King :: Queen :: Jack :: Ten :: Nil
fifthCard = index 4 hand
sixthCard = index 5 hand
hand : Vect 5 Card
hand = Ace :: King :: Queen :: Jack :: Ten :: Nil
fifthCard = index 4 hand
sixthCard = index 5 hand
hand : Vect 5 Card
hand = Ace :: King :: Queen :: Jack :: Ten :: Nil
fifthCard = index 4 hand
sixthCard = index 5 hand
index : Fin n -> Vect n e -> e
Dependent types
Study Case 4: Vector
interface UserRepository {
private User findUserById(String id);
}
interface UserRepository {
private Optional<User> findUserById(String id);
}
Optional<User>
User
∅
connection.connect(function() {
connection.query(myQuery, callback);
});
connection.query(myQuery, callback);
connection.connect(otherCallback);
Temporal coupling
MySQL connector in Node.js
connector.connect(function(connection) {
connection.query(myQuery, callback);
});
Temporal coupling
const passwordInput = new PasswordInput();
// ...
if (passwordInput.isValid()) {
saveForm(passwordInput);
}
type Unvalidated = {_type: "Unvalidated"};
type Validated = {_type: "Validated"};
class PasswordInput<T> {
}
static create(): PasswordInput<Unvalidated> {
return new PasswordInput();
}
validate(): Error | PasswordInput<Validated> {
// ...
}
saveForm(input: PasswordInput<Validated>) {
// ...
}
Phantom type
Make your code more explicit to avoid errors
Implicit is error-prone
upload:
destinationPath: ./upload/images
maxFileSize: 1024
upload:
destinationPath: ./upload/images/
maxFileSizeInMo: 1024
Paths.get(uploadPath, fileName);
Encapsulation
User user = new User();
user.setId(userId);
user.setName("Jordane");
user.setPicture(picture);
User user = new User(userId, name);
user.setPicture(picture);
class UserService {
@Autowired
private UserRepository userRepository;
public UserService() {
// ...
}
}
class UserService {
@Autowired
public UserService(UserRepository userRepository) {
// ...
}
}
Honesty
getName : User -> String
getName user =
user.name
user.password = 'p0wn3d';
function getName(user: User) {
return user.name;
}
sendLoveLetterToMyEx();
getMessage : User -> String
getMessage myUser =
"Hello " ++ (getName myUser)
Prevent mistakes before they can occur
Poka-Yoke
Avoid
Mistakes
_______ _ _ _____ _____ _____ _____
|__ __| | | |_ _|/ ____| |_ _|/ ____|
| | | |__| | | | | (___ | | | (___
| | | __ | | | \___ \ | | \___ \
| | | | | |_| |_ ____) | _| |_ ____) |
|_| |_| |_|_____|_____/ |_____|_____/
_____ _____ ____ _____ _ _ _____ _______ _____ ____ _ _
| __ \| __ \ / __ \| __ \| | | |/ ____|__ __|_ _/ __ \| \ | |
| |__) | |__) | | | | | | | | | | | | | | || | | | \| |
| ___/| _ /| | | | | | | | | | | | | | || | | | . ` |
| | | | \ \| |__| | |__| | |__| | |____ | | _| || |__| | |\ |
|_| |_| \_\\____/|_____/ \____/ \_____| |_| |_____\____/|_| \_|
admin@server /project/build:
- Why did the error occur?
- Can I make it impossible?
- If not, can I catch it earlier?
- Can I make it more difficult to happen?
Technical ? Human ?
Fail loudly (Option, Either, Exception)
Refactoring, focus on types
Feedback cycle, unit tests
Final thoughts
- Get an IDE / linter
- Reduce your domain (ADT)
- Kill null and undefined
I call it my billion-dollar mistake. It was the invention of the null reference in 1965. [...] This has led to innumerable errors, vulnerabilities, and system crashes [...]
– Tony Hoare –
Final thoughts
- Get an IDE / linter
- Reduce your domain (ADT)
- Kill null and undefined with 🔥
- Make tests
- Be honest – Modelize uncertainty
When you start to modelize your uncertainty, you quickly realize it's everywhere in your codebases
Final thoughts
- Get an IDE / linter
- Reduce your domain (ADT)
- Kill null and undefined with 🔥
- Make tests
- Be honest – Modelize uncertainty
- Automatize everything
- Use a langage that helps you
¯\_(ツ)_/¯
17K+ LoC
200K+ LoC
Links
- Making impossible states impossible by Richard Feldman
- Fear, Trust and JavaScript by Nicholas Kariniemi
- Bug Free. By Design by Johan Martinsson
- Software: Designing with types by Mark Seemann
- Parse, don't validate by Alexis King
Slides : https://bit.ly/2zDQlXR
@JoGrenat
Thank you!
Slides : https://bit.ly/2zDQlXR
@JoGrenat
Bug-free by design
By ereold
Bug-free by design
- 3,212