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

Email

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: 
  1. Why did the error occur?
  2. Can I make it impossible?
  3. If not, can I catch it earlier?
  4. 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

  1. Get an IDE / linter
  2. Reduce your domain (ADT)
  3. 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

  1. Get an IDE / linter
  2. Reduce your domain (ADT)
  3. Kill null and undefined with 🔥
  4. Make tests
  5. Be honest – Modelize uncertainty

When you start to modelize your uncertainty, you quickly realize it's everywhere in your codebases

Final thoughts

  1. Get an IDE / linter
  2. Reduce your domain (ADT)
  3. Kill null and undefined with 🔥
  4. Make tests
  5. Be honest – Modelize uncertainty
  6. Automatize everything
  7. Use a langage that helps you

¯\_(ツ)_/¯

17K+ LoC

200K+ LoC

Links

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,252